2025-06-12 20:58:22 +00:00
|
|
|
"use client"
|
|
|
|
|
|
|
|
import { useState, useEffect } from "react"
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
|
|
import { Input } from "@/components/ui/input"
|
|
|
|
import { Label } from "@/components/ui/label"
|
|
|
|
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 { Badge } from "@/components/ui/badge"
|
|
|
|
import { Textarea } from "@/components/ui/textarea"
|
|
|
|
import {
|
|
|
|
Dialog,
|
|
|
|
DialogContent,
|
|
|
|
DialogDescription,
|
|
|
|
DialogHeader,
|
|
|
|
DialogTitle,
|
|
|
|
DialogTrigger,
|
|
|
|
} from "@/components/ui/dialog"
|
|
|
|
import { Edit2, Plus, Save, X, Loader2, RefreshCw, History } from "lucide-react"
|
|
|
|
|
|
|
|
// Usar as variáveis de ambiente para as URLs
|
|
|
|
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api/v1"
|
|
|
|
|
|
|
|
interface PriceHistory {
|
|
|
|
startDate: string
|
|
|
|
endDate: string | null
|
|
|
|
price: number
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Product {
|
|
|
|
_id: string
|
|
|
|
name: string
|
|
|
|
description: string
|
|
|
|
priceHistory: PriceHistory[]
|
|
|
|
createdAt: string
|
|
|
|
updatedAt: string
|
|
|
|
__v: number
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ProductsResponse {
|
|
|
|
msg: string
|
|
|
|
products: Product[]
|
|
|
|
}
|
|
|
|
|
|
|
|
interface CreateProductRequest {
|
|
|
|
name: string
|
|
|
|
description: string
|
|
|
|
price: number
|
|
|
|
}
|
|
|
|
|
|
|
|
interface UpdateProductRequest {
|
|
|
|
name?: string
|
|
|
|
description?: string
|
|
|
|
price?: number
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function ProductManagement() {
|
|
|
|
const [products, setProducts] = useState<Product[]>([])
|
|
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
const [error, setError] = useState("")
|
|
|
|
const [success, setSuccess] = useState("")
|
|
|
|
|
|
|
|
// Estados para criação de produto
|
|
|
|
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
|
|
|
|
const [isCreating, setIsCreating] = useState(false)
|
|
|
|
const [createForm, setCreateForm] = useState<CreateProductRequest>({
|
|
|
|
name: "",
|
|
|
|
description: "",
|
|
|
|
price: 0,
|
|
|
|
})
|
|
|
|
|
|
|
|
// Estados para edição de produto
|
|
|
|
const [editingId, setEditingId] = useState<string | null>(null)
|
|
|
|
const [editForm, setEditForm] = useState<UpdateProductRequest>({
|
|
|
|
name: "",
|
|
|
|
description: "",
|
|
|
|
price: 0,
|
|
|
|
})
|
|
|
|
const [isUpdating, setIsUpdating] = useState(false)
|
|
|
|
|
|
|
|
// Estados para histórico de preços
|
|
|
|
const [historyDialogOpen, setHistoryDialogOpen] = useState(false)
|
|
|
|
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null)
|
|
|
|
|
|
|
|
const getAuthHeaders = () => {
|
|
|
|
const token = localStorage.getItem("access_token")
|
|
|
|
return {
|
|
|
|
Authorization: `Bearer ${token}`,
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const fetchProducts = async () => {
|
|
|
|
setIsLoading(true)
|
|
|
|
setError("")
|
|
|
|
|
|
|
|
try {
|
|
|
|
const response = await fetch(`${API_BASE_URL}/billing/products`, {
|
|
|
|
method: "GET",
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
})
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
const result: ProductsResponse = await response.json()
|
|
|
|
setProducts(result.products || [])
|
|
|
|
} else {
|
|
|
|
const errorData = await response.json()
|
|
|
|
// Para erro 400, exibir a mensagem específica da API
|
|
|
|
if (response.status === 400 && errorData.msg) {
|
|
|
|
setError(errorData.msg)
|
|
|
|
} else {
|
|
|
|
setError(errorData.message || errorData.msg || "Erro ao buscar produtos")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (err) {
|
2025-06-12 21:32:27 +00:00
|
|
|
console.log(err)
|
2025-06-12 20:58:22 +00:00
|
|
|
setError("Erro de conexão com o servidor")
|
|
|
|
} finally {
|
|
|
|
setIsLoading(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const createProduct = async () => {
|
|
|
|
if (!createForm.name || !createForm.description || createForm.price <= 0) {
|
|
|
|
setError("Preencha todos os campos corretamente")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
setIsCreating(true)
|
|
|
|
setError("")
|
|
|
|
setSuccess("")
|
|
|
|
|
|
|
|
try {
|
|
|
|
const response = await fetch(`${API_BASE_URL}/billing/product`, {
|
|
|
|
method: "POST",
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
body: JSON.stringify(createForm),
|
|
|
|
})
|
|
|
|
|
2025-06-12 21:32:27 +00:00
|
|
|
if (response.ok) {
|
2025-06-12 20:58:22 +00:00
|
|
|
setSuccess("Produto criado com sucesso!")
|
|
|
|
setCreateForm({ name: "", description: "", price: 0 })
|
|
|
|
setIsCreateDialogOpen(false)
|
|
|
|
await fetchProducts()
|
|
|
|
} else {
|
|
|
|
const errorData = await response.json()
|
|
|
|
// Para erro 400, exibir a mensagem específica da API
|
|
|
|
if (response.status === 400 && errorData.msg) {
|
|
|
|
setError(errorData.msg)
|
|
|
|
} else {
|
|
|
|
setError(errorData.message || errorData.msg || "Erro ao criar produto")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (err) {
|
2025-06-12 21:32:27 +00:00
|
|
|
console.log(err)
|
2025-06-12 20:58:22 +00:00
|
|
|
setError("Erro de conexão com o servidor")
|
|
|
|
} finally {
|
|
|
|
setIsCreating(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const updateProduct = async (productId: string) => {
|
|
|
|
// Verificar se há pelo menos um campo para atualizar
|
|
|
|
if (Object.keys(editForm).length === 0) {
|
|
|
|
setError("Nenhuma alteração para salvar")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Se o preço estiver definido, verificar se é válido
|
|
|
|
if (editForm.price !== undefined && (isNaN(editForm.price) || editForm.price <= 0)) {
|
|
|
|
setError("Digite um preço válido")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
setIsUpdating(true)
|
|
|
|
setError("")
|
|
|
|
setSuccess("")
|
|
|
|
|
|
|
|
try {
|
|
|
|
const response = await fetch(`${API_BASE_URL}/billing/product/${productId}`, {
|
|
|
|
method: "PATCH",
|
|
|
|
headers: getAuthHeaders(),
|
|
|
|
body: JSON.stringify(editForm),
|
|
|
|
})
|
|
|
|
|
2025-06-12 21:32:27 +00:00
|
|
|
if (response.ok) {
|
2025-06-12 20:58:22 +00:00
|
|
|
setSuccess("Produto atualizado com sucesso!")
|
|
|
|
setEditingId(null)
|
|
|
|
setEditForm({})
|
|
|
|
await fetchProducts()
|
|
|
|
} else {
|
|
|
|
const errorData = await response.json()
|
|
|
|
// Para erro 400, exibir a mensagem específica da API
|
|
|
|
if (response.status === 400 && errorData.msg) {
|
|
|
|
setError(errorData.msg)
|
|
|
|
} else {
|
|
|
|
setError(errorData.message || errorData.msg || "Erro ao atualizar produto")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (err) {
|
2025-06-12 21:32:27 +00:00
|
|
|
console.log(err)
|
2025-06-12 20:58:22 +00:00
|
|
|
setError("Erro de conexão com o servidor")
|
|
|
|
} finally {
|
|
|
|
setIsUpdating(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const startEdit = (product: Product) => {
|
|
|
|
setEditingId(product._id)
|
|
|
|
const currentPrice = getCurrentPrice(product)
|
|
|
|
setEditForm({
|
|
|
|
name: product.name,
|
|
|
|
description: product.description,
|
|
|
|
price: currentPrice,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const cancelEdit = () => {
|
|
|
|
setEditingId(null)
|
|
|
|
setEditForm({})
|
|
|
|
}
|
|
|
|
|
|
|
|
const getCurrentPrice = (product: Product): number => {
|
|
|
|
return product.priceHistory.find((p) => p.endDate === null)?.price || 0
|
|
|
|
}
|
|
|
|
|
|
|
|
const formatDate = (dateString: string): string => {
|
|
|
|
return new Date(dateString).toLocaleString("pt-BR")
|
|
|
|
}
|
|
|
|
|
|
|
|
const formatDateCompact = (dateString: string): string => {
|
|
|
|
const date = new Date(dateString)
|
|
|
|
return `${date.getDate().toString().padStart(2, "0")}/${(date.getMonth() + 1).toString().padStart(2, "0")}/${date.getFullYear()}, ${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}`
|
|
|
|
}
|
|
|
|
|
|
|
|
const showHistory = (product: Product) => {
|
|
|
|
setSelectedProduct(product)
|
|
|
|
setHistoryDialogOpen(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
fetchProducts()
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="space-y-6">
|
|
|
|
{/* Cabeçalho com botão de criar */}
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
<div>
|
|
|
|
<h3 className="text-lg font-medium">Produtos Cadastrados</h3>
|
|
|
|
<p className="text-sm text-gray-500">Gerencie produtos e seus preços</p>
|
|
|
|
</div>
|
|
|
|
<div className="flex gap-2">
|
|
|
|
<Button onClick={fetchProducts} disabled={isLoading} variant="outline">
|
|
|
|
{isLoading ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <RefreshCw className="mr-2 h-4 w-4" />}
|
|
|
|
Atualizar
|
|
|
|
</Button>
|
|
|
|
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
|
|
|
|
<DialogTrigger asChild>
|
|
|
|
<Button>
|
|
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
|
|
Novo Produto
|
|
|
|
</Button>
|
|
|
|
</DialogTrigger>
|
|
|
|
<DialogContent>
|
|
|
|
<DialogHeader>
|
|
|
|
<DialogTitle>Criar Novo Produto</DialogTitle>
|
|
|
|
<DialogDescription>Preencha as informações do novo produto</DialogDescription>
|
|
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4">
|
|
|
|
<div className="space-y-2">
|
|
|
|
<Label htmlFor="name">Nome do Produto *</Label>
|
|
|
|
<Input
|
|
|
|
id="name"
|
|
|
|
value={createForm.name}
|
|
|
|
onChange={(e) => setCreateForm((prev) => ({ ...prev, name: e.target.value }))}
|
|
|
|
placeholder="Ex: Produto HIT"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
|
|
<Label htmlFor="description">Descrição *</Label>
|
|
|
|
<Textarea
|
|
|
|
id="description"
|
|
|
|
value={createForm.description}
|
|
|
|
onChange={(e) => setCreateForm((prev) => ({ ...prev, description: e.target.value }))}
|
|
|
|
placeholder="Descreva o produto..."
|
|
|
|
rows={3}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
|
|
<Label htmlFor="price">Preço Inicial *</Label>
|
|
|
|
<Input
|
|
|
|
id="price"
|
|
|
|
type="number"
|
|
|
|
step="0.001"
|
|
|
|
value={createForm.price}
|
|
|
|
onChange={(e) =>
|
|
|
|
setCreateForm((prev) => ({ ...prev, price: Number.parseFloat(e.target.value) || 0 }))
|
|
|
|
}
|
|
|
|
placeholder="0.060"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="flex gap-2 pt-4">
|
|
|
|
<Button onClick={createProduct} disabled={isCreating} className="flex-1">
|
|
|
|
{isCreating ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Save className="mr-2 h-4 w-4" />}
|
|
|
|
Criar Produto
|
|
|
|
</Button>
|
|
|
|
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)} disabled={isCreating}>
|
|
|
|
Cancelar
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</DialogContent>
|
|
|
|
</Dialog>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{error && (
|
|
|
|
<Alert variant="destructive">
|
|
|
|
<AlertDescription>{error}</AlertDescription>
|
|
|
|
</Alert>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{success && (
|
|
|
|
<Alert>
|
|
|
|
<AlertDescription>{success}</AlertDescription>
|
|
|
|
</Alert>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{/* Tabela de Produtos */}
|
|
|
|
<Card>
|
|
|
|
<CardHeader>
|
|
|
|
<CardTitle>Lista de Produtos ({products.length} produtos)</CardTitle>
|
|
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
|
|
|
{products.length > 0 ? (
|
|
|
|
<div className="overflow-x-auto">
|
|
|
|
<Table>
|
|
|
|
<TableHeader>
|
|
|
|
<TableRow>
|
|
|
|
<TableHead>Nome</TableHead>
|
|
|
|
<TableHead>Descrição</TableHead>
|
|
|
|
<TableHead>Preço Atual</TableHead>
|
|
|
|
<TableHead>Criado em</TableHead>
|
|
|
|
<TableHead>Atualizado em</TableHead>
|
|
|
|
<TableHead>Ações</TableHead>
|
|
|
|
</TableRow>
|
|
|
|
</TableHeader>
|
|
|
|
<TableBody>
|
|
|
|
{products.map((product) => (
|
|
|
|
<TableRow key={product._id}>
|
|
|
|
<TableCell>
|
|
|
|
{editingId === product._id ? (
|
|
|
|
<Input
|
|
|
|
value={editForm.name}
|
|
|
|
onChange={(e) => setEditForm((prev) => ({ ...prev, name: e.target.value }))}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<div className="font-medium">{product.name}</div>
|
|
|
|
)}
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{editingId === product._id ? (
|
|
|
|
<Textarea
|
|
|
|
value={editForm.description}
|
|
|
|
onChange={(e) => setEditForm((prev) => ({ ...prev, description: e.target.value }))}
|
|
|
|
rows={2}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<div className="max-w-xs truncate" title={product.description}>
|
|
|
|
{product.description}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{editingId === product._id ? (
|
|
|
|
<Input
|
|
|
|
type="number"
|
|
|
|
step="0.001"
|
|
|
|
value={editForm.price}
|
|
|
|
onChange={(e) =>
|
|
|
|
setEditForm((prev) => ({ ...prev, price: Number.parseFloat(e.target.value) || 0 }))
|
|
|
|
}
|
|
|
|
className="w-24"
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
<Badge
|
|
|
|
variant="secondary"
|
|
|
|
className="cursor-pointer hover:bg-gray-200 transition-colors"
|
|
|
|
onClick={() => showHistory(product)}
|
|
|
|
title="Clique para ver o histórico de preços"
|
|
|
|
>
|
|
|
|
${getCurrentPrice(product)}
|
|
|
|
</Badge>
|
|
|
|
{product.priceHistory && product.priceHistory.length > 1 && (
|
|
|
|
<Badge variant="outline" className="text-xs">
|
|
|
|
{product.priceHistory.length} alterações
|
|
|
|
</Badge>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>{formatDate(product.createdAt)}</TableCell>
|
|
|
|
<TableCell>{formatDate(product.updatedAt)}</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
<div className="flex gap-2">
|
|
|
|
{editingId === product._id ? (
|
|
|
|
<>
|
|
|
|
<Button size="sm" onClick={() => updateProduct(product._id)} disabled={isUpdating}>
|
|
|
|
{isUpdating ? (
|
|
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
|
|
) : (
|
|
|
|
<Save className="h-4 w-4" />
|
|
|
|
)}
|
|
|
|
</Button>
|
|
|
|
<Button size="sm" variant="outline" onClick={cancelEdit} disabled={isUpdating}>
|
|
|
|
<X className="h-4 w-4" />
|
|
|
|
</Button>
|
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
<Button size="sm" variant="outline" onClick={() => startEdit(product)}>
|
|
|
|
<Edit2 className="h-4 w-4" />
|
|
|
|
</Button>
|
|
|
|
<Button size="sm" variant="outline" onClick={() => showHistory(product)}>
|
|
|
|
<History className="h-4 w-4" />
|
|
|
|
</Button>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
))}
|
|
|
|
</TableBody>
|
|
|
|
</Table>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<div className="text-center py-8 text-gray-500">
|
|
|
|
{isLoading ? "Carregando..." : "Nenhum produto encontrado. Crie seu primeiro produto!"}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</CardContent>
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
{/* Dialog de Histórico de Preços - Ajustado para caber na tela */}
|
|
|
|
<Dialog open={historyDialogOpen} onOpenChange={setHistoryDialogOpen}>
|
|
|
|
<DialogContent className="w-[95%] max-w-3xl max-h-[90vh] overflow-y-auto">
|
|
|
|
<DialogHeader>
|
|
|
|
<DialogTitle>Histórico de Preços - {selectedProduct?.name}</DialogTitle>
|
|
|
|
<DialogDescription>
|
|
|
|
Visualize todas as alterações de preço deste produto ({selectedProduct?.priceHistory?.length || 0}{" "}
|
|
|
|
registros)
|
|
|
|
</DialogDescription>
|
|
|
|
</DialogHeader>
|
|
|
|
{selectedProduct && selectedProduct.priceHistory && selectedProduct.priceHistory.length > 0 ? (
|
|
|
|
<div className="space-y-4">
|
|
|
|
<div className="overflow-x-auto">
|
|
|
|
<Table className="w-full">
|
|
|
|
<TableHeader>
|
|
|
|
<TableRow>
|
|
|
|
<TableHead>Data Início</TableHead>
|
|
|
|
<TableHead>Data Fim</TableHead>
|
|
|
|
<TableHead>Preço</TableHead>
|
|
|
|
<TableHead>Status</TableHead>
|
|
|
|
<TableHead>Duração</TableHead>
|
|
|
|
</TableRow>
|
|
|
|
</TableHeader>
|
|
|
|
<TableBody>
|
|
|
|
{selectedProduct.priceHistory
|
|
|
|
.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())
|
|
|
|
.map((history, index) => {
|
|
|
|
const startDate = new Date(history.startDate)
|
|
|
|
const endDate = history.endDate ? new Date(history.endDate) : null
|
|
|
|
const duration = endDate
|
|
|
|
? Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24))
|
|
|
|
: Math.ceil((new Date().getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24))
|
|
|
|
|
|
|
|
return (
|
|
|
|
<TableRow key={index} className={history.endDate === null ? "bg-blue-50" : ""}>
|
|
|
|
<TableCell className="text-sm">{formatDateCompact(history.startDate)}</TableCell>
|
|
|
|
<TableCell className="text-sm">
|
|
|
|
{history.endDate ? formatDateCompact(history.endDate) : "Em vigor"}
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
<Badge variant={history.endDate === null ? "default" : "secondary"}>
|
|
|
|
${history.price}
|
|
|
|
</Badge>
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
<Badge variant={history.endDate === null ? "default" : "outline"}>
|
|
|
|
{history.endDate === null ? "Atual" : "Histórico"}
|
|
|
|
</Badge>
|
|
|
|
</TableCell>
|
|
|
|
<TableCell className="text-sm text-gray-600">
|
|
|
|
{duration} {duration === 1 ? "dia" : "dias"}
|
|
|
|
{history.endDate === null && " (em vigor)"}
|
|
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</TableBody>
|
|
|
|
</Table>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* Resumo do histórico - Simplificado para economizar espaço */}
|
|
|
|
<div className="mt-4 p-3 bg-gray-50 rounded-lg">
|
|
|
|
<h4 className="font-medium text-sm text-gray-700 mb-2">Resumo do Histórico</h4>
|
|
|
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
|
|
<div>
|
|
|
|
<span className="text-gray-500">Total de alterações:</span>
|
|
|
|
<div className="font-medium">{selectedProduct.priceHistory.length}</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<span className="text-gray-500">Preço inicial:</span>
|
|
|
|
<div className="font-medium">
|
|
|
|
$
|
|
|
|
{selectedProduct.priceHistory
|
|
|
|
.sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime())[0]
|
|
|
|
?.price.toFixed(3)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<span className="text-gray-500">Preço atual:</span>
|
|
|
|
<div className="font-medium">${getCurrentPrice(selectedProduct)}</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<span className="text-gray-500">Produto criado em:</span>
|
|
|
|
<div className="font-medium">{formatDateCompact(selectedProduct.createdAt)}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<div className="text-center py-8 text-gray-500">
|
|
|
|
Nenhum histórico de preços encontrado para este produto.
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</DialogContent>
|
|
|
|
</Dialog>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|