feat: initial commit — asaas-checkout template white-label
Template genérico de checkout com ASAAS, parametrizado via env vars. Inclui fluxo completo: checkout → pedido → polling → webhook → admin. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
139
components/product-table.tsx
Normal file
139
components/product-table.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog"
|
||||
import { deleteProduto } from "@/lib/actions"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
import type { Produto } from "@/lib/supabase"
|
||||
|
||||
interface ProductTableProps {
|
||||
products: Produto[]
|
||||
}
|
||||
|
||||
export function ProductTable({ products }: ProductTableProps) {
|
||||
const router = useRouter()
|
||||
const [selectedProduct, setSelectedProduct] = useState<Produto | null>(null)
|
||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!selectedProduct) return
|
||||
const result = await deleteProduto(selectedProduct.id)
|
||||
if (result.success) {
|
||||
toast({ title: "Produto excluído com sucesso." })
|
||||
setIsDeleteDialogOpen(false)
|
||||
setSelectedProduct(null)
|
||||
router.refresh()
|
||||
} else {
|
||||
toast({ title: "Erro ao excluir produto.", variant: "destructive" })
|
||||
}
|
||||
}
|
||||
|
||||
const precoFormatado = (centavos: number) =>
|
||||
(centavos / 100).toLocaleString("pt-BR", { style: "currency", currency: "BRL" })
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Nome</TableHead>
|
||||
<TableHead>Tipo</TableHead>
|
||||
<TableHead>Validade</TableHead>
|
||||
<TableHead>Mídia</TableHead>
|
||||
<TableHead>Preço</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{products.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} className="h-24 text-center text-muted-foreground">
|
||||
Nenhum produto cadastrado.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
products.map((product) => (
|
||||
<TableRow key={product.id} className="cursor-pointer hover:bg-muted/50" onClick={() => setSelectedProduct(product)}>
|
||||
<TableCell className="font-medium">{product.nome}</TableCell>
|
||||
<TableCell><Badge variant="outline">{product.tipo}</Badge></TableCell>
|
||||
<TableCell>{product.validade}</TableCell>
|
||||
<TableCell>{product.midia ?? "—"}</TableCell>
|
||||
<TableCell>{precoFormatado(product.preco_centavos)}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={product.ativo ? "default" : "secondary"}>
|
||||
{product.ativo ? "Ativo" : "Inativo"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button variant="ghost" size="sm" onClick={(e) => { e.stopPropagation(); setSelectedProduct(product) }}>
|
||||
Detalhes
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
{/* Modal de detalhes */}
|
||||
<Dialog open={!!selectedProduct && !isDeleteDialogOpen} onOpenChange={(open) => !open && setSelectedProduct(null)}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
{selectedProduct && (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{selectedProduct.nome}</DialogTitle>
|
||||
<DialogDescription>{selectedProduct.descricao ?? "Sem descrição"}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-2 gap-4 mt-4 text-sm">
|
||||
<div><span className="font-medium">Tipo:</span> {selectedProduct.tipo}</div>
|
||||
<div><span className="font-medium">Validade:</span> {selectedProduct.validade}</div>
|
||||
<div><span className="font-medium">Mídia:</span> {selectedProduct.midia ?? "—"}</div>
|
||||
<div><span className="font-medium">Preço:</span> {precoFormatado(selectedProduct.preco_centavos)}</div>
|
||||
<div><span className="font-medium">Status:</span> {selectedProduct.ativo ? "Ativo" : "Inativo"}</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 mt-6">
|
||||
<Button asChild variant="outline">
|
||||
<Link href={`/produtos/${selectedProduct.id}`}>Ver página</Link>
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={() => setIsDeleteDialogOpen(true)}>
|
||||
Excluir
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Confirm delete */}
|
||||
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Confirmar Exclusão</DialogTitle>
|
||||
<DialogDescription>
|
||||
Tem certeza que deseja excluir "{selectedProduct?.nome}"? Esta ação não pode ser desfeita.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsDeleteDialogOpen(false)}>Cancelar</Button>
|
||||
<Button variant="destructive" onClick={handleDelete}>Excluir</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user