From ee808c35bf1882f416f09899a0bd2da842de7c15 Mon Sep 17 00:00:00 2001 From: Benito <zs22016070@estudiantes.uv.mx> Date: Wed, 12 Mar 2025 19:56:23 -0600 Subject: [PATCH] Add react-tabs for improved report visualization and integrate Supabase for sales data fetching --- ventaboletos/package-lock.json | 13 ++ ventaboletos/package.json | 1 + .../src/components/vistas/Reporte.jsx | 198 ++++++++++-------- ventaboletos/src/pages/api/comprar-boletos.js | 43 ++-- 4 files changed, 154 insertions(+), 101 deletions(-) diff --git a/ventaboletos/package-lock.json b/ventaboletos/package-lock.json index f7e1d44..3fd4bb6 100644 --- a/ventaboletos/package-lock.json +++ b/ventaboletos/package-lock.json @@ -23,6 +23,7 @@ "next": "15.1.7", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-tabs": "^6.1.0", "recharts": "^2.15.1", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", @@ -5400,6 +5401,18 @@ } } }, + "node_modules/react-tabs": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.1.0.tgz", + "integrity": "sha512-6QtbTRDKM+jA/MZTTefvigNxo0zz+gnBTVFw2CFVvq+f2BuH0nF0vDLNClL045nuTAdOoK/IL1vTP0ZLX0DAyQ==", + "dependencies": { + "clsx": "^2.0.0", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", diff --git a/ventaboletos/package.json b/ventaboletos/package.json index c943a37..3430c76 100644 --- a/ventaboletos/package.json +++ b/ventaboletos/package.json @@ -24,6 +24,7 @@ "next": "15.1.7", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-tabs": "^6.1.0", "recharts": "^2.15.1", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", diff --git a/ventaboletos/src/components/vistas/Reporte.jsx b/ventaboletos/src/components/vistas/Reporte.jsx index 7d2ccf3..9062889 100644 --- a/ventaboletos/src/components/vistas/Reporte.jsx +++ b/ventaboletos/src/components/vistas/Reporte.jsx @@ -1,106 +1,138 @@ -import React, { useState, useRef } from "react"; -import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useState, useEffect } from "react"; +import { createClient } from "@supabase/supabase-js"; +import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; +import "react-tabs/style/react-tabs.css"; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts"; -import jsPDF from "jspdf"; import html2canvas from "html2canvas"; +import jsPDF from "jspdf"; -const Reporte = () => { - const chartRef = useRef(null); // Referencia para capturar la gráfica +const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY +); - // Datos simulados de ventas - const ventasDiarias = [ - { fecha: "2025-03-01", boletos: 20 }, - { fecha: "2025-03-02", boletos: 35 }, - { fecha: "2025-03-03", boletos: 50 }, - ]; +export default function Reporte() { + const [ventas, setVentas] = useState([]); + const [filtro, setFiltro] = useState("diario"); // Estado para el filtro de fecha (diario, semanal, mensual) - const ventasSemanales = [ - { semana: "Semana 1", boletos: 120 }, - { semana: "Semana 2", boletos: 150 }, - { semana: "Semana 3", boletos: 170 }, - ]; + // Función para obtener los datos de ventas de Supabase + useEffect(() => { + const fetchVentas = async () => { + const { data, error } = await supabase.from("ventas").select("*"); + if (error) { + console.error("Error al obtener las ventas:", error.message); + } else { + console.log("Datos obtenidos:", data); + setVentas(data); + } + }; - const ventasMensuales = [ - { mes: "Enero", boletos: 500 }, - { mes: "Febrero", boletos: 650 }, - { mes: "Marzo", boletos: 700 }, - ]; + fetchVentas(); + }, []); - const [selectedTab, setSelectedTab] = useState("diario"); + // Función para filtrar las ventas según el rango de fechas + const filtrarVentas = () => { + const hoy = new Date(); + let fechaInicio; - // Obtener datos según la pestaña activa - const getData = () => { - switch (selectedTab) { + switch (filtro) { case "diario": - return ventasDiarias; + fechaInicio = new Date(hoy.setHours(0, 0, 0, 0)); // Inicio del día + return ventas.filter((venta) => { + const fechaVenta = new Date(venta.fecha_venta); + return fechaVenta >= fechaInicio; + }); case "semanal": - return ventasSemanales; + const inicioSemana = hoy.getDate() - hoy.getDay(); // Día lunes de esta semana + fechaInicio = new Date(hoy.setDate(inicioSemana)); + return ventas.filter((venta) => { + const fechaVenta = new Date(venta.fecha_venta); + return fechaVenta >= fechaInicio; + }); case "mensual": - return ventasMensuales; + fechaInicio = new Date(hoy.getFullYear(), hoy.getMonth(), 1); // Primer día del mes + return ventas.filter((venta) => { + const fechaVenta = new Date(venta.fecha_venta); + return fechaVenta >= fechaInicio; + }); default: - return []; + return ventas; } }; - // Calcular el total de boletos vendidos - const totalBoletos = getData().reduce((acc, item) => acc + item.boletos, 0); - - // Función para generar y descargar el PDF + // Función para generar el PDF const generarPDF = async () => { - const pdf = new jsPDF(); - pdf.setFontSize(18); - pdf.text("Reporte de Ventas", 10, 10); - pdf.setFontSize(14); - pdf.text(`Tipo de Reporte: ${selectedTab.toUpperCase()}`, 10, 20); - pdf.text(`Total de Boletos Vendidos: ${totalBoletos}`, 10, 30); - - // Capturar la gráfica como imagen - if (chartRef.current) { - const canvas = await html2canvas(chartRef.current); - const imgData = canvas.toDataURL("image/png"); - pdf.addImage(imgData, "PNG", 10, 40, 180, 90); - } - - pdf.save(`reporte_${selectedTab}.pdf`); + const input = document.getElementById("reporte"); + const canvas = await html2canvas(input); + const imgData = canvas.toDataURL("image/png"); + const pdf = new jsPDF("p", "mm", "a4"); + pdf.addImage(imgData, "PNG", 10, 10, 190, 0); + pdf.save("Reporte_Ventas.pdf"); }; + // Datos filtrados según el filtro seleccionado + const ventasFiltradas = filtrarVentas(); + return ( - <div className="w-full max-w-3xl mx-auto p-4"> - <h2 className="text-2xl font-bold mb-4">Reporte de Ventas</h2> - <Tabs defaultValue="diario" onValueChange={setSelectedTab} className="w-full"> - <TabsList className="flex space-x-2 mb-4"> - <TabsTrigger value="diario">Diario</TabsTrigger> - <TabsTrigger value="semanal">Semanal</TabsTrigger> - <TabsTrigger value="mensual">Mensual</TabsTrigger> - <img - src="/pdf.svg" - alt="pdf" - className="h-7 w-7 cursor-pointer" - onClick={generarPDF} - /> - </TabsList> + <div> + <h1>Reporte de Ventas</h1> - <Card> - <CardHeader> - <CardTitle> - Total de Boletos Vendidos: <span className="text-blue-600">{totalBoletos}</span> - </CardTitle> - </CardHeader> - <CardContent ref={chartRef}> - <ResponsiveContainer width="100%" height={300}> - <BarChart data={getData()}> - <XAxis dataKey={selectedTab === "diario" ? "fecha" : selectedTab === "semanal" ? "semana" : "mes"} /> - <YAxis /> - <Tooltip /> - <Bar dataKey="boletos" fill="#3b82f6" /> - </BarChart> - </ResponsiveContainer> - </CardContent> - </Card> + {/* Botón para generar PDF */} + <button onClick={generarPDF}>Descargar PDF</button> + + {/* Filtros de fechas */} + <div> + <button onClick={() => setFiltro("diario")}>Diario</button> + <button onClick={() => setFiltro("semanal")}>Semanal</button> + <button onClick={() => setFiltro("mensual")}>Mensual</button> + </div> + + <Tabs> + <TabList> + <Tab>Tabla</Tab> + <Tab>Gráfico de Ventas</Tab> + </TabList> + + {/* Sección de Tabla */} + <TabPanel> + <div id="reporte"> + <table border="1"> + <thead> + <tr> + <th>ID Venta</th> + <th>ID Boleto</th> + <th>ID Vendedor</th> + <th>Fecha Venta</th> + <th>Monto</th> + </tr> + </thead> + <tbody> + {ventasFiltradas.map((venta) => ( + <tr key={venta.venta_id}> + <td>{venta.venta_id}</td> + <td>{venta.boleto_id}</td> + <td>{venta.vendedor_id}</td> + <td>{new Date(venta.fecha_venta).toLocaleString()}</td> + <td>${venta.monto}</td> + </tr> + ))} + </tbody> + </table> + </div> + </TabPanel> + + {/* Sección de Gráfico */} + <TabPanel> + <ResponsiveContainer width="100%" height={300}> + <BarChart data={ventasFiltradas} margin={{ top: 20, right: 30, left: 20, bottom: 5 }}> + <XAxis dataKey="venta_id" /> + <YAxis /> + <Tooltip /> + <Bar dataKey="monto" fill="#8884d8" /> + </BarChart> + </ResponsiveContainer> + </TabPanel> </Tabs> </div> ); -}; - -export default Reporte; +} diff --git a/ventaboletos/src/pages/api/comprar-boletos.js b/ventaboletos/src/pages/api/comprar-boletos.js index 93e484a..204f5a0 100644 --- a/ventaboletos/src/pages/api/comprar-boletos.js +++ b/ventaboletos/src/pages/api/comprar-boletos.js @@ -1,24 +1,31 @@ -import { supabaseClient } from "@/utils/supabase"; +import { createClient } from "@supabase/supabase-js"; + +const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY +); export default async function handler(req, res) { - if (req.method === "POST") { + if (req.method !== "POST") { + return res.status(405).json({ error: "Método no permitido" }); + } + + try { const { boletos } = req.body; - try { - const { data, error } = await supabaseClient - .from("boletos_comprados") - .insert(boletos); - - if (error) { - throw error; - } - - res.status(200).json({ message: "Compra realizada con éxito", data }); - } catch (error) { - console.error("Error al insertar boletos:", error); - res.status(500).json({ message: "Error al procesar la compra", error }); + if (!boletos || boletos.length === 0) { + return res.status(400).json({ error: "No hay boletos para procesar" }); } - } else { - res.status(405).json({ message: "Método no permitido" }); + + const { data, error } = await supabase.from("ventas").insert(boletos); + + if (error) { + throw error; + } + + res.status(200).json({ message: "Boletos comprados con éxito", data }); + } catch (error) { + console.error("Error en la API de compra:", error.message); + res.status(500).json({ error: error.message }); } -} \ No newline at end of file +}