added card total cost

master
adriano 2025-06-13 18:14:16 -03:00
parent 306ba95bc2
commit b0fa541fbc
10 changed files with 330 additions and 89 deletions

View File

@ -20,6 +20,8 @@ flask-bcrypt = "*"
flask-cors = "*"
mypy = "*"
requests = "*"
redis = "*"
flask-redis = "*"
[dev-packages]

26
backend/Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "3798a8dec1f82bd3b8d8e6089e8110ae5e152e1e76d9b78810ba5b4cfab33c2f"
"sha256": "424e88c981f7de9a38bc3d2c0189df4f377bb72daa06d4b7a0b742a51b7ad51e"
},
"pipfile-spec": 6,
"requires": {
@ -276,6 +276,15 @@
"markers": "python_version >= '3.9' and python_version < '4'",
"version": "==4.7.1"
},
"flask-redis": {
"hashes": [
"sha256:8d79eef4eb1217095edab603acc52f935b983ae4b7655ee7c82c0dfd87315d17",
"sha256:e1fccc11e7ea35c2a4d68c0b9aa58226a098e45e834d615c7b6c4928b01ddd6c"
],
"index": "pypi",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.4.0"
},
"flask-restx": {
"hashes": [
"sha256:4f3d3fa7b6191fcc715b18c201a12cd875176f92ba4acc61626ccfd571ee1728",
@ -664,12 +673,12 @@
},
"pydantic": {
"hashes": [
"sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a",
"sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7"
"sha256:12b45cfb4af17e555d3c6283d0b55271865fb0b43cc16dd0d52749dc7abf70e7",
"sha256:a24478d2be1b91b6d3bc9597439f69ed5e87f68ebd285d86f7c7932a084b72e7"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==2.11.5"
"version": "==2.11.6"
},
"pydantic-core": {
"hashes": [
@ -906,6 +915,15 @@
],
"version": "==2025.2"
},
"redis": {
"hashes": [
"sha256:c8ddf316ee0aab65f04a11229e94a64b2618451dab7a67cb2f77eb799d872d5e",
"sha256:e821f129b75dde6cb99dd35e5c76e8c49512a5a0d8dfdc560b2fbd44b85ca977"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==6.2.0"
},
"referencing": {
"hashes": [
"sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa",

View File

@ -1,7 +1,7 @@
from flask import Flask
from flask_restx import Api
from .config import Config
from .extensions import init_mongo, init_jwt
from .extensions import init_mongo, init_jwt, init_redis
from .errors.handlers import register_error_handlers
from .routes.usage_routes import usage_ns
from .routes.auth_routes import auth_ns
@ -16,6 +16,7 @@ def create_app():
init_mongo(app)
init_jwt(app)
init_redis(app)
api = Api(
app,

View File

@ -26,6 +26,6 @@ class Config:
BILLING_API_URL = os.getenv("BILLING_API_URL")
BILLING_API_TOKEN = os.getenv("BILLING_API_TOKEN")
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379")

View File

@ -1,10 +1,16 @@
from pymongo import MongoClient
from flask_jwt_extended import JWTManager
from flask_redis import FlaskRedis
jwt = JWTManager()
redis_client = FlaskRedis()
def init_mongo(app):
app.mongo_client = MongoClient(app.config["MONGO_URI"])
def init_redis(app):
redis_client.init_app(app)
def init_jwt(app):
jwt.init_app(app)

View File

@ -1,4 +1,5 @@
import os
import os
import hashlib
from bson import json_util
from flask_restx import Resource
from flask_jwt_extended import jwt_required
@ -16,18 +17,20 @@ from app.docs.usage_models import (
transcription_data_query_params, usage_ns)
TMP_DIR = '/tmp'
@usage_ns.route('/export/trascription')
class TranscriptionExport(Resource):
@usage_ns.doc(security='Bearer Auth')
@usage_ns.doc(params=transcription_data_query_params)
@usage_ns.response(200, 'success')
@usage_ns.response(400, 'Validation error')
@usage_ns.response(400, 'Validation error')
@usage_ns.response(404, 'File not found')
@jwt_required()
def get(self):
"""
Export transcription report in XLSX.
"""
"""
data = {
"company_id": request.args.get("companyId", type=str),
"start_date": request.args.get("startDate"),
@ -37,35 +40,39 @@ class TranscriptionExport(Resource):
validated = TranscriptionRequest(**data)
service = TranscriptionReportService(
validated.company_id,
validated.start_date,
validated.end_date
# Cria um nome de arquivo baseado nos parâmetros (evita caracteres inválidos)
hash_key = hashlib.md5(f"{validated.company_id}_{validated.start_date}_{validated.end_date}_{validated.who}".encode()).hexdigest()
filename = f"transcription_{hash_key}.xlsx"
filepath = os.path.join(TMP_DIR, filename)
# Verifica se o arquivo já existe
if not os.path.exists(filepath):
# Gera o relatório e salva no caminho desejado
service = TranscriptionReportService(
validated.company_id,
validated.start_date,
validated.end_date
)
if validated.who == "hit":
generated_path = service.reportDataXLSX(hit_report=True)
else:
generated_path = service.reportDataXLSX()
if not os.path.exists(generated_path):
return {"error": "File generation failed"}, 500
# Move o arquivo gerado para o TMP
os.rename(generated_path, filepath)
# Retorna o arquivo
return send_file(
path_or_file=filepath,
as_attachment=True,
download_name=os.path.basename(filepath),
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
if validated.who == "hit":
report_path = service.reportDataXLSX(hit_report=True)
else:
report_path = service.reportDataXLSX()
if not os.path.exists(report_path):
return {"error": "File not found"}, 404
@after_this_request
def remove_file(response):
try:
os.remove(report_path)
except Exception as delete_error:
current_app.logger.warning(f"Error trying to delete file: {delete_error}")
return response
return send_file(
path_or_file=report_path,
as_attachment=True,
download_name=os.path.basename(report_path),
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
@usage_ns.route('/data/trascription')
class TranscriptionUsageData(Resource):
@ -95,13 +102,14 @@ class TranscriptionUsageData(Resource):
validated.start_date,
validated.end_date
)
if validated.who == "hit":
result = service.reportData(page=page, page_size=page_size,hit_report=True)
else:
result = service.reportData(page=page, page_size=page_size)
return {"success": True, "data": {"data": result["data"], "pagination": result["pagination"]}}, 200
return {"success": True, "data": {"data": result["data"], "pagination": result["pagination"], "cost":result["cost"]}}, 200

View File

@ -0,0 +1,13 @@
from app.extensions import redis_client
def cache_set(key, value, ex=0):
if ex:
redis_client.set(key, value, ex=ex)
else:
redis_client.set(key, value)
def cache_get(key):
return redis_client.get(key)
def cache_del(key):
redis_client.delete(key)

View File

@ -7,11 +7,12 @@ from openpyxl.utils.dataframe import dataframe_to_rows
from openpyxl.styles import Font, PatternFill
import pandas as pd
import os
import hashlib
from typing import List, Dict, Any, Optional
from app.utils.mysql_query import execute_query
from app.utils.calc_api_usage import calculate_api_usage
import math
from app.services.redis_service import cache_del, cache_get, cache_set
import json
class TranscriptionReportService:
def __init__(self, company_id: str, start_date: str, end_date: str):
@ -155,10 +156,7 @@ class TranscriptionReportService:
"page_size": page_size,
"total_pages": (total + page_size - 1) // page_size
}
def _fetch_mysql_data(self, hit_report: Optional[bool] = False)-> List[Dict[str, Any]]:
@ -185,12 +183,7 @@ class TranscriptionReportService:
uniqueid, src, dst;"""
rows = execute_query(self.company_id, sql)
if hit_report:
# collection = self.mongo_client["billing-api"]["api_pricings"]
# result_stt = collection.find({"type": "stt"})
# result_stt = [{"product": t["product"], "clientPrice": t["clientPrice"]} for t in result_stt if "clientPrice" in t]
if hit_report:
for row in rows:
row["companyId"] = self.company_id
@ -198,6 +191,7 @@ class TranscriptionReportService:
row["custo_hit"] = f"{float(rowMongo["totalCost"])}"
row["qtd_token_input"] = rowMongo.get('usageByType', {}).get('input', 0)
row["qtd_token_output"] = rowMongo.get('usageByType', {}).get('output', 0)
row["total_min"] = f"{(int(row['total_billsec']) / 60):.2f}"
self.client_price_row(products, row)
self.formate_properties(row)
@ -209,6 +203,8 @@ class TranscriptionReportService:
self.client_price_row(products, row)
self.formate_properties(row)
return rows
def formate_properties(self, row):
@ -258,6 +254,7 @@ class TranscriptionReportService:
"src": "Origem",
"dst": "Destino",
"total_billsec": "Quantidade de segundos",
"total_min": "Duração (Em minutos)",
"custo_hit": "Custo HIT",
"qtd_token_input": "Quantidade de tokens(input)",
"qtd_token_output": "Quantidade de tokens(output)",
@ -311,7 +308,34 @@ class TranscriptionReportService:
wb.save(path)
return path
def _reportDataTotalCost(self):
hash_key = hashlib.md5(f"{self.company_id}_{self.start_date}_{self.end_date}".encode()).hexdigest()
sum_key = f"sum_total_cost_{hash_key}"
if data := cache_get(f'report_model_usage:total_cost:{sum_key}'):
return json.loads(data)
self._fetch_mongo_data(all_data=True)
mysql_data = self._fetch_mysql_data(hit_report=True)
total_cost_hit = sum(float(item.get('custo_hit') or 0) for item in mysql_data)
total_client_cost = sum(float(item.get('client_total_cost') or 0) for item in mysql_data)
sum_total_cost = {
'company_id': self.company_id,
'start_date': self.start_date,
'end_date': self.end_date,
'total_cost_hit': total_cost_hit,
'total_client_cost': total_client_cost
}
cache_set(f'report_model_usage:total_cost:{sum_key}', json.dumps(sum_total_cost))
return sum_total_cost
def reportDataXLSX(self, hit_report: Optional[bool] = False) -> str:
self._fetch_mongo_data(all_data=True)
@ -333,7 +357,8 @@ class TranscriptionReportService:
return {
"pagination": mongo_data,
"data": mysql_data
"data": mysql_data,
"cost": self._reportDataTotalCost()
}

View File

@ -48,7 +48,7 @@ export default function Dashboard() {
<header className="bg-white shadow">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center py-6">
<h1 className="text-3xl font-bold text-gray-900">Dashboard - Sistema de Transcrição</h1>
<h1 className="text-3xl font-bold text-gray-900">Dashboard - Custo de Produtos</h1>
<Button onClick={handleLogout} variant="outline">
<LogOut className="mr-2 h-4 w-4" />
Sair

View File

@ -1,6 +1,6 @@
"use client"
import { useState } from "react"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
@ -8,12 +8,35 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
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"
import { Download, Search, Loader2, DollarSign, TrendingUp, Calculator } 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:
function formatDateBr(dateStr:string) {
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,
})
)
}
interface ClientTranscriptionData {
uniqueid: string
@ -33,7 +56,8 @@ interface HitTranscriptionData {
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
total_min: number,
custo_hit: string // Custo HIT
client_total_cost: string // Custo Cliente
client_price: string // Preço Cliente por Minuto
start_call: string // Inicio
@ -42,7 +66,32 @@ interface HitTranscriptionData {
type TranscriptionData = ClientTranscriptionData | HitTranscriptionData
// Primeiro, vamos atualizar as interfaces para refletir a nova estrutura da API:
// 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
}
}
interface PaginationInfo {
total: number
page: number
@ -55,13 +104,14 @@ interface ApiResponse {
data: {
data: TranscriptionData[]
pagination: PaginationInfo
cost?: CostInfo
}
}
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 [costInfo, setCostInfo] = useState<CostInfo | null>(null)
const [exchangeRate, setExchangeRate] = useState<number | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [isExporting, setIsExporting] = useState(false)
const [error, setError] = useState("")
@ -70,10 +120,9 @@ export default function TranscriptionTable() {
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:
// Estados para paginação
const [pagination, setPagination] = useState<PaginationInfo>({
total: 0,
page: 1,
@ -91,7 +140,25 @@ export default function TranscriptionTable() {
}
}
// Atualizar a função fetchData para incluir parâmetros de paginação:
// 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
}
const fetchData = async (page: number = currentPage) => {
if (!companyId || !startDate || !endDate) {
setError("Preencha todos os campos obrigatórios")
@ -102,6 +169,9 @@ export default function TranscriptionTable() {
setError("")
try {
// Buscar cotação do dólar em paralelo
const ratePromise = fetchExchangeRate()
const params = new URLSearchParams({
companyId,
startDate,
@ -120,15 +190,20 @@ export default function TranscriptionTable() {
if (result.success && result.data) {
setData(result.data.data || [])
setPagination(result.data.pagination)
setCostInfo(result.data.cost || null)
setCurrentPage(result.data.pagination.page)
} else {
setData([])
setCostInfo(null)
setPagination({ total: 0, page: 1, page_size: 20, total_pages: 0 })
}
} else {
const errorData = await response.json()
setError(errorData.message || "Erro ao buscar dados")
}
// Aguardar a cotação do dólar
await ratePromise
} catch (err) {
console.log("====> Erro de conexão com o servidor: ", err)
setError("Erro de conexão com o servidor")
@ -180,7 +255,7 @@ export default function TranscriptionTable() {
}
}
// Adicionar funções de navegação de páginas:
// Funções de navegação de páginas
const goToPage = (page: number) => {
if (page >= 1 && page <= pagination.total_pages) {
setCurrentPage(page)
@ -193,8 +268,84 @@ export default function TranscriptionTable() {
const goToNextPage = () => goToPage(currentPage + 1)
const goToLastPage = () => goToPage(pagination.total_pages)
// 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()
}, [])
return (
<div className="space-y-6">
{/* 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">
{formatDateBr(costInfo.start_date)} até {formatDateBr(costInfo.end_date)}
</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>
)}
{/* Filtros */}
<Card>
<CardHeader>
@ -224,8 +375,6 @@ export default function TranscriptionTable() {
<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 />
@ -237,12 +386,11 @@ export default function TranscriptionTable() {
</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) => {
onValueChange={(value: string) => {
setPageSize(Number(value))
setCurrentPage(1)
}}
@ -281,10 +429,18 @@ export default function TranscriptionTable() {
</Alert>
)}
{/* 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>
)}
{/* 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 && (
@ -295,8 +451,6 @@ export default function TranscriptionTable() {
</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>
@ -308,14 +462,15 @@ export default function TranscriptionTable() {
<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>
<TableHead>Custo HIT</TableHead>
<TableHead>Custo Cliente</TableHead>
<TableHead>Preço Cliente por Minuto</TableHead>
<TableHead>Segundos</TableHead>
<TableHead>Minutos</TableHead>
<TableHead>Custo Cliente (R$)</TableHead>
<TableHead>Preço Cliente p/ Minuto (R$)</TableHead>
<TableHead>Custo HIT ($)</TableHead>
<TableHead>Início</TableHead>
<TableHead>Fim</TableHead>
<TableHead>Tokens (Input)</TableHead>
<TableHead>Tokens (Output)</TableHead>
</>
) : (
<>
@ -324,8 +479,8 @@ export default function TranscriptionTable() {
<TableHead>Destino</TableHead>
<TableHead>Início</TableHead>
<TableHead>Duração (s)</TableHead>
<TableHead>Total (min)</TableHead>
<TableHead>Custo Cliente</TableHead>
<TableHead>Duração (m)</TableHead>
<TableHead>Custo (R$)</TableHead>
</>
)}
</TableRow>
@ -340,27 +495,40 @@ export default function TranscriptionTable() {
<TableCell>{(item as HitTranscriptionData).src || "-"}</TableCell>
<TableCell>{(item as HitTranscriptionData).dst || "-"}</TableCell>
<TableCell>{(item as HitTranscriptionData).total_billsec || "-"}</TableCell>
<TableCell>{(item as HitTranscriptionData).total_min || "-"}</TableCell>
<TableCell>
{(item as HitTranscriptionData).client_total_cost
? `R$ ${(item as HitTranscriptionData).client_total_cost.slice(0, 4)}`
: "-"}
</TableCell>
<TableCell>
{(item as HitTranscriptionData).client_price
? `R$ ${(item as HitTranscriptionData).client_price}`
: "-"}
</TableCell>
<TableCell>
{(item as HitTranscriptionData).custo_hit
? `$ ${(item as HitTranscriptionData).custo_hit.slice(0, 4)}`
: "-"}
</TableCell>
<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>
<TableCell>
{(item as HitTranscriptionData).custo_hit ? `$${(item as HitTranscriptionData).custo_hit}` : "-"}
</TableCell>
<TableCell>{(item as HitTranscriptionData).client_total_cost || "-"}</TableCell>
<TableCell>
{(item as HitTranscriptionData).client_price ? `$${(item as HitTranscriptionData).client_price}` : "-"}
</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>{formatDateTime((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>
<TableCell>
{(item as ClientTranscriptionData).client_total_cost
? `R$ ${(item as ClientTranscriptionData).client_total_cost.slice(0, 4)}`
: "-"}
</TableCell>
</>
)}
</TableRow>