345 lines
11 KiB
TypeScript
345 lines
11 KiB
TypeScript
"use client"
|
|
|
|
import type React 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"
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Alert, AlertDescription } from "@/components/ui/alert"
|
|
import { Textarea } from "@/components/ui/textarea"
|
|
import { Loader2, Save, RefreshCw } from "lucide-react"
|
|
|
|
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://127.0.0.1:5000/api/v1"
|
|
|
|
interface ModelPrice {
|
|
_id: { $oid: string }
|
|
provider: string
|
|
product: string
|
|
currency: string
|
|
price: string
|
|
billingBy: string
|
|
billingUnit: number
|
|
type: string
|
|
createdAt: { $date: string }
|
|
updatedAt: { $date: string }
|
|
__v: number
|
|
}
|
|
|
|
interface ModelPricesResponse {
|
|
success: boolean
|
|
data: ModelPrice[]
|
|
}
|
|
|
|
interface CostUpdateResponse {
|
|
success: boolean
|
|
docs_updated: number
|
|
}
|
|
|
|
interface CostUpdatePayload {
|
|
product: string
|
|
start_date: string
|
|
end_date: string
|
|
price: string
|
|
billing_unit: number
|
|
company_ids?: string[]
|
|
}
|
|
|
|
|
|
export default function CostUpdateForm() {
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [isLoadingData, setIsLoadingData] = useState(false)
|
|
const [error, setError] = useState("")
|
|
const [success, setSuccess] = useState("")
|
|
|
|
// Estados para os dados dos combos
|
|
const [products, setProducts] = useState<string[]>([])
|
|
const [billingOptions, setBillingOptions] = useState<{ billingBy: string; billingUnit: number }[]>([])
|
|
|
|
const [formData, setFormData] = useState({
|
|
product: "",
|
|
start_date: "",
|
|
end_date: "",
|
|
price: "",
|
|
billing_unit: "",
|
|
company_ids: "",
|
|
})
|
|
|
|
const getAuthHeaders = () => {
|
|
const token = localStorage.getItem("access_token")
|
|
return {
|
|
Authorization: `Bearer ${token}`,
|
|
"Content-Type": "application/json",
|
|
}
|
|
}
|
|
|
|
// Função para buscar dados dos modelos
|
|
const fetchModelPrices = async () => {
|
|
setIsLoadingData(true)
|
|
setError("")
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/usage/model/prices?type=stt`, {
|
|
headers: getAuthHeaders(),
|
|
})
|
|
|
|
if (response.ok) {
|
|
const result: ModelPricesResponse = await response.json()
|
|
if (result.success && Array.isArray(result.data)) {
|
|
// Extrair produtos únicos
|
|
const uniqueProducts = [...new Set(result.data.map((item) => item.product))]
|
|
setProducts(uniqueProducts)
|
|
|
|
// Extrair opções de billing únicas
|
|
const uniqueBillingOptions = result.data.reduce(
|
|
(acc, item) => {
|
|
const existing = acc.find(
|
|
(option) => option.billingBy === item.billingBy && option.billingUnit === item.billingUnit,
|
|
)
|
|
if (!existing) {
|
|
acc.push({
|
|
billingBy: item.billingBy,
|
|
billingUnit: item.billingUnit,
|
|
})
|
|
}
|
|
return acc
|
|
},
|
|
[] as { billingBy: string; billingUnit: number }[],
|
|
)
|
|
|
|
setBillingOptions(uniqueBillingOptions)
|
|
}
|
|
} else {
|
|
const errorData = await response.json()
|
|
setError(errorData.message || "Erro ao buscar dados dos modelos")
|
|
}
|
|
} catch (err) {
|
|
console.log("Erro de conexão com o servidor: ",err)
|
|
setError("Erro de conexão com o servidor")
|
|
} finally {
|
|
setIsLoadingData(false)
|
|
}
|
|
}
|
|
|
|
// Carregar dados ao montar o componente
|
|
useEffect(() => {
|
|
fetchModelPrices()
|
|
}, [])
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setIsLoading(true)
|
|
setError("")
|
|
setSuccess("")
|
|
|
|
try {
|
|
// Converte company_ids de string para array (se fornecido)
|
|
let company_ids: string[] | undefined
|
|
if (formData.company_ids.trim()) {
|
|
company_ids = formData.company_ids
|
|
.split(",")
|
|
.map((id) => id.trim())
|
|
.filter((id) => id.length > 0)
|
|
}
|
|
|
|
const payload: CostUpdatePayload = {
|
|
product: formData.product,
|
|
start_date: formData.start_date,
|
|
end_date: formData.end_date,
|
|
price: formData.price,
|
|
billing_unit: Number(formData.billing_unit),
|
|
}
|
|
|
|
// Só adiciona company_ids se foi fornecido
|
|
if (company_ids && company_ids.length > 0) {
|
|
payload.company_ids = company_ids
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE_URL}/usage/cost`, {
|
|
method: "PATCH",
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
if (response.ok) {
|
|
const result: CostUpdateResponse = await response.json()
|
|
if (result.success) {
|
|
setSuccess(`Custos atualizados com sucesso! ${result.docs_updated} registros foram atualizados.`)
|
|
setFormData({
|
|
product: "",
|
|
start_date: "",
|
|
end_date: "",
|
|
price: "",
|
|
billing_unit: "",
|
|
company_ids: "",
|
|
})
|
|
} else {
|
|
setError("Erro na resposta do servidor")
|
|
}
|
|
} else {
|
|
const errorData = await response.json()
|
|
setError(errorData.message || "Erro ao atualizar custos")
|
|
}
|
|
} catch (err) {
|
|
console.log("====> Erro de conexão com o servidor: ", err)
|
|
setError("Erro de conexão com o servidor")
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleInputChange = (field: string, value: string) => {
|
|
setFormData((prev) => ({ ...prev, [field]: value }))
|
|
}
|
|
|
|
const handleBillingChange = (billingBy: string) => {
|
|
// Encontrar o billingUnit correspondente
|
|
const selectedOption = billingOptions.find((option) => option.billingBy === billingBy)
|
|
if (selectedOption) {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
billing_unit: selectedOption.billingUnit.toString(),
|
|
}))
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center justify-between">
|
|
Atualizar Custos de Uso
|
|
<Button variant="outline" size="sm" onClick={fetchModelPrices} disabled={isLoadingData}>
|
|
{isLoadingData ? <Loader2 className="h-4 w-4 animate-spin" /> : <RefreshCw className="h-4 w-4" />}
|
|
</Button>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="product">Produto *</Label>
|
|
<Select
|
|
value={formData.product}
|
|
onValueChange={(value) => handleInputChange("product", value)}
|
|
disabled={isLoadingData}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder={isLoadingData ? "Carregando..." : "Selecione um produto"} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{products.map((product) => (
|
|
<SelectItem key={product} value={product}>
|
|
{product}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="billing_by">Unidade de Cobrança *</Label>
|
|
<Select
|
|
value={
|
|
billingOptions.find((option) => option.billingUnit.toString() === formData.billing_unit)?.billingBy ||
|
|
""
|
|
}
|
|
onValueChange={handleBillingChange}
|
|
disabled={isLoadingData}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder={isLoadingData ? "Carregando..." : "Selecione a unidade"} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{billingOptions.map((option, index) => (
|
|
<SelectItem key={`${option.billingBy}-${option.billingUnit}-${index}`} value={option.billingBy}>
|
|
{option.billingBy} ({option.billingUnit})
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="start_date">Data Inicial *</Label>
|
|
<Input
|
|
id="start_date"
|
|
type="date"
|
|
value={formData.start_date}
|
|
onChange={(e) => handleInputChange("start_date", e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="end_date">Data Final *</Label>
|
|
<Input
|
|
id="end_date"
|
|
type="date"
|
|
value={formData.end_date}
|
|
onChange={(e) => handleInputChange("end_date", e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2 md:col-span-2">
|
|
<Label htmlFor="price">Preço *</Label>
|
|
<Input
|
|
id="price"
|
|
type="text"
|
|
value={formData.price}
|
|
onChange={(e) => handleInputChange("price", e.target.value)}
|
|
placeholder="0.024"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2 md:col-span-2">
|
|
<Label htmlFor="company_ids">IDs das Empresas (opcional)</Label>
|
|
<Textarea
|
|
id="company_ids"
|
|
value={formData.company_ids}
|
|
onChange={(e) => handleInputChange("company_ids", e.target.value)}
|
|
placeholder="123, 456, 789 (separados por vírgula - deixe vazio para atualizar todas)"
|
|
rows={3}
|
|
/>
|
|
<p className="text-sm text-gray-500">
|
|
Digite os IDs das empresas separados por vírgula. Deixe vazio para atualizar todas as empresas.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Campo oculto para mostrar o billing_unit selecionado */}
|
|
{formData.billing_unit && (
|
|
<div className="space-y-2 md:col-span-2">
|
|
<Label>Valor da Unidade de Cobrança</Label>
|
|
<Input value={formData.billing_unit} disabled className="bg-gray-50" />
|
|
<p className="text-sm text-gray-500">
|
|
Este valor é definido automaticamente baseado na unidade de cobrança selecionada.
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{error && (
|
|
<Alert variant="destructive">
|
|
<AlertDescription>{error}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
{success && (
|
|
<Alert>
|
|
<AlertDescription>{success}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
<Button type="submit" disabled={isLoading || isLoadingData} className="w-full">
|
|
{isLoading ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Save className="mr-2 h-4 w-4" />}
|
|
Atualizar Custos
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|