Reports Table - Copy this React, Tailwind Component to your project
Pero esas no son las tablas que te pase, son 5 y no son esas import React, { useState, useEffect } from "react"; import { Container, Row, Col, Card, Button, FormControl, InputGroup, Dropdown, } from "react-bootstrap"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import config from "../config/config"; const ReportsTable = () => { const [workedHoursData, setWorkedHoursData] = useState([]); const [punctualityData, setPunctualityData] = useState([]); const [attendanceData, setAttendanceData] = useState([]); const [absenceData, setAbsenceData] = useState([]); const [extraHoursData, setExtraHoursData] = useState([]); // Filtros const [workedHoursFilters, setWorkedHoursFilters] = useState({ fromDate: null, toDate: null, }); const [punctualityFilters, setPunctualityFilters] = useState({ state: "", rut: "", fromDate: null, toDate: null, }); const [attendanceFilters, setAttendanceFilters] = useState({ state: "", rut: "", fromDate: null, toDate: null, }); const [absenceFilters, setAbsenceFilters] = useState({ type: "", justification: "", rut: "", fromDate: null, toDate: null, }); const [extraHoursFilters, setExtraHoursFilters] = useState({ extra: "", fromDate: null, toDate: null, }); useEffect(() => { fetchData(); }, []); const fetchData = () => { fetch(`${config.apiUrl}/reports/worked-hours`) .then((res) => res.json()) .then(setWorkedHoursData); fetch(`${config.apiUrl}/reports/punctuality`) .then((res) => res.json()) .then(setPunctualityData); fetch(`${config.apiUrl}/reports/attendance`) .then((res) => res.json()) .then(setAttendanceData); fetch(`${config.apiUrl}/reports/absence`) .then((res) => res.json()) .then(setAbsenceData); fetch(`${config.apiUrl}/reports/extra-hours`) .then((res) => res.json()) .then(setExtraHoursData); }; const handleFilter = (endpoint, filters, setData) => { // Convertimos las fechas a string con formato YYYY-MM-DD si existen const params = { ...filters }; if (filters.fromDate) { params.fromDate = filters.fromDate.toISOString().split("T")[0]; } if (filters.toDate) { params.toDate = filters.toDate.toISOString().split("T")[0]; } let query = Object.entries(params) .filter(([_, value]) => value) .map(([key, value]) => `${key}=${value}`) .join("&"); fetch(`${config.apiUrl}/reports/${endpoint}?${query}`) .then((res) => res.json()) .then(setData); }; const downloadCSV = (data, filename) => { if (!data || data.length === 0) { alert("No hay datos para descargar."); return; } const keys = Object.keys(data[0]); const header = keys.join(","); const rows = data.map((item) => keys .map((key) => { const val = item[key] !== null && item[key] !== undefined ? item[key].toString() : ""; return `"${val.replace(/"/g, '""')}"`; }) .join(",") ); const csvContent = [header, ...rows].join("\n"); const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.setAttribute("download", filename); document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }; // Función para obtener el nombre completo según los campos disponibles const getFullName = (entry) => { // Revisamos si tiene employee_name if (entry.employee_name) { return entry.employee_name; } // Si no, usamos name_employee y lastname_employee si existen if (entry.name_employee && entry.lastname_employee) { return `${entry.name_employee} ${entry.lastname_employee}`; } return ""; // Si no hay datos, cadena vacía }; return ( <Container> <h3>Reportes (Tablas)</h3> <Row> {/* Horas Trabajadas */} <Col md={12} className="mb-4"> <Card> <Card.Body> <h5>Reporte de Horas Trabajadas</h5> <Row> <Col md={3}> <label>Desde</label> <DatePicker selected={workedHoursFilters.fromDate} onChange={(date) => setWorkedHoursFilters((prev) => ({ ...prev, fromDate: date, })) } dateFormat="yyyy-MM-dd" /> </Col> <Col md={3}> <label>Hasta</label> <DatePicker selected={workedHoursFilters.toDate} onChange={(date) => setWorkedHoursFilters((prev) => ({ ...prev, toDate: date, })) } dateFormat="yyyy-MM-dd" /> </Col> <Col md={6} className="d-flex align-items-end"> <Button onClick={() => handleFilter("worked-hours", workedHoursFilters, setWorkedHoursData) } > Filtrar </Button> <Button variant="secondary" className="ms-2" onClick={() => { setWorkedHoursFilters({ fromDate: null, toDate: null }); fetchData(); }} > Sin Filtro </Button> <Button variant="success" className="ms-2" onClick={() => downloadCSV(workedHoursData, "horas_trabajadas.csv")} > Descargar CSV </Button> </Col> </Row> <div className="mt-3 table-responsive"> <table className="table table-striped"> <thead> <tr> <th>RUT</th> <th>Nombre Completo</th> <th>Fecha</th> <th>Horas Totales (hrs decimales)</th> </tr> </thead> <tbody> {workedHoursData.map((entry, index) => { const totalHours = entry.worked_hours ? (entry.worked_hours.hours || 0) + ((entry.worked_hours.minutes || 0) / 60) + ((entry.worked_hours.seconds || 0) / 3600) : 0; const fullName = getFullName(entry); return ( <tr key={index}> <td>{entry.rut_employee}</td> <td>{fullName}</td> <td>{entry.work_date}</td> <td>{totalHours.toFixed(2)}</td> </tr> ); })} </tbody> </table> </div> </Card.Body> </Card> </Col> {/* Puntualidad */} <Col md={12} className="mb-4"> <Card> <Card.Body> <h5>Reporte de Puntualidad</h5> <Row> <Col md={2}> <label>Desde</label> <DatePicker selected={punctualityFilters.fromDate} onChange={(date) => setPunctualityFilters((prev) => ({ ...prev, fromDate: date, })) } dateFormat="yyyy-MM-dd" /> </Col> <Col md={2}> <label>Hasta</label> <DatePicker selected={punctualityFilters.toDate} onChange={(date) => setPunctualityFilters((prev) => ({ ...prev, toDate: date, })) } dateFormat="yyyy-MM-dd" /> </Col> <Col md={3}> <InputGroup> <FormControl placeholder="RUT" value={punctualityFilters.rut} onChange={(e) => setPunctualityFilters((prev) => ({ ...prev, rut: e.target.value, })) } /> </InputGroup> </Col> <Col md={3}> <Dropdown onSelect={(state) => setPunctualityFilters((prev) => ({ ...prev, state, })) } > <Dropdown.Toggle> {punctualityFilters.state || "Estado"} </Dropdown.Toggle> <Dropdown.Menu> <Dropdown.Item eventKey="">Sin Filtro</Dropdown.Item> <Dropdown.Item eventKey="Atrasado">Atrasado</Dropdown.Item> <Dropdown.Item eventKey="Puntual">Puntual</Dropdown.Item> </Dropdown.Menu> </Dropdown> </Col> <Col md={2} className="d-flex align-items-end"> <Button onClick={() => handleFilter("punctuality", punctualityFilters, setPunctualityData) } > Filtrar </Button> <Button variant="secondary" className="ms-2" onClick={() => { setPunctualityFilters({ state: "", rut: "", fromDate: null, toDate: null }); fetchData(); }} > Sin Filtro </Button> <Button variant="success" className="ms-2" onClick={() => downloadCSV(punctualityData, "puntualidad.csv")} > CSV </Button> </Col> </Row> <div className="mt-3 table-responsive"> <table className="table table-striped"> <thead> <tr> <th>RUT</th> <th>Nombre Completo</th> <th>Estado</th> <th>Fecha</th> </tr> </thead> <tbody> {punctualityData.map((entry, index) => { const fullName = getFullName(entry); return ( <tr key={index}> <td>{entry.rut_employee}</td> <td>{fullName}</td> <td>{entry.status}</td> <td>{entry.work_date}</td> </tr> ); })} </tbody> </table> </div> </Card.Body> </Card> </Col> {/* Ausencias */} <Col md={12} className="mb-4"> <Card> <Card.Body> <h5>Reporte de Ausencias</h5> <Row> <Col md={2}> <label>Desde</label> <DatePicker selected={absenceFilters.fromDate} onChange={(date) => setAbsenceFilters((prev) => ({ ...prev, fromDate: date, })) } dateFormat="yyyy-MM-dd" /> </Col> <Col md={2}> <label>Hasta</label> <DatePicker selected={absenceFilters.toDate} onChange={(date) => setAbsenceFilters((prev) => ({ ...prev, toDate: date, })) } dateFormat="yyyy-MM-dd" /> </Col> <Col md={2}> <FormControl placeholder="RUT" value={absenceFilters.rut} onChange={(e) => setAbsenceFilters((prev) => ({ ...prev, rut: e.target.value, })) } /> </Col> <Col md={2}> <FormControl placeholder="Tipo de Ausencia" value={absenceFilters.type} onChange={(e) => setAbsenceFilters((prev) => ({ ...prev, type: e.target.value, })) } /> </Col> <Col md={2}> <Dropdown onSelect={(justification) => setAbsenceFilters((prev) => ({ ...prev, justification, })) } > <Dropdown.Toggle> {absenceFilters.justification || "Justificación"} </Dropdown.Toggle> <Dropdown.Menu> <Dropdown.Item eventKey="">Sin Filtro</Dropdown.Item> <Dropdown.Item eventKey="Justificado"> Justificado </Dropdown.Item> <Dropdown.Item eventKey="No justificado"> No justificado </Dropdown.Item> </Dropdown.Menu> </Dropdown> </Col> <Col md={2} className="d-flex align-items-end"> <Button onClick={() => handleFilter("absence", absenceFilters, setAbsenceData) } > Filtrar </Button> <Button variant="secondary" className="ms-2" onClick={() => { setAbsenceFilters({ type: "", justification: "", rut: "", fromDate: null, toDate: null, }); fetchData(); }} > Sin Filtro </Button> <Button variant="success" className="ms-2" onClick={() => downloadCSV(absenceData, "ausencias.csv")} > CSV </Button> </Col> </Row> <div className="mt-3 table-responsive"> <table className="table table-striped"> <thead> <tr> <th>RUT</th> <th>Nombre Completo</th> <th>Tipo</th> <th>Justificación</th> <th>Fecha (Inicio Ausencia)</th> </tr> </thead> <tbody> {absenceData.map((entry, index) => { const fullName = getFullName(entry); return ( <tr key={index}> <td>{entry.rut_employee}</td> <td>{fullName}</td> <td>{entry.absence_type || ""}</td> <td>{entry.justification_status}</td> <td>{entry.absence_start_date}</td> </tr> ); })} </tbody> </table> </div> </Card.Body> </Card> </Col> {/* Asistencia Diaria */} <Col md={12} className="mb-4"> <Card> <Card.Body> <h5>Reporte de Asistencia Diaria</h5> <Row> <Col md={2}> <label>Desde</label> <DatePicker selected={attendanceFilters.fromDate} onChange={(date) => setAttendanceFilters((prev) => ({ ...prev, fromDate: date, })) } dateFormat="yyyy-MM-dd" /> </Col> <Col md={2}> <label>Hasta</label> <DatePicker selected={attendanceFilters.toDate} onChange={(date) => setAttendanceFilters((prev) => ({ ...prev, toDate: date, })) } dateFormat="yyyy-MM-dd" /> </Col> <Col md={4}> <InputGroup> <FormControl placeholder="RUT" value={attendanceFilters.rut} onChange={(e) => setAttendanceFilters((prev) => ({ ...prev, rut: e.target.value, })) } /> </InputGroup> </Col> <Col md={2}> <Dropdown onSelect={(state) => setAttendanceFilters((prev) => ({ ...prev, state, })) } > <Dropdown.Toggle> {attendanceFilters.state || "Estado"} </Dropdown.Toggle> <Dropdown.Menu> <Dropdown.Item eventKey="">Sin Filtro</Dropdown.Item> <Dropdown.Item eventKey="Presente">Presente</Dropdown.Item> <Dropdown.Item eventKey="Ausente">Ausente</Dropdown.Item> </Dropdown.Menu> </Dropdown> </Col> <Col md={2} className="d-flex align-items-end"> <Button onClick={() => handleFilter("attendance", attendanceFilters, setAttendanceData) } > Filtrar </Button> <Button variant="secondary" className="ms-2" onClick={() => { setAttendanceFilters({ state: "", rut: "", fromDate: null, toDate: null }); fetchData(); }} > Sin Filtro </Button> <Button variant="success" className="ms-2" onClick={() => downloadCSV(attendanceData, "asistencia_diaria.csv")} > CSV </Button> </Col> </Row> <div className="mt-3 table-responsive"> <table className="table table-striped"> <thead> <tr> <th>RUT</th> <th>Nombre Completo</th> <th>Estado</th> <th>Fecha</th> </tr> </thead> <tbody> {attendanceData.map((entry, index) => { const fullName = getFullName(entry); return ( <tr key={index}> <td>{entry.rut_employee}</td> <td>{fullName}</td> <td>{entry.attendance_status}</td> <td>{entry.work_date}</td> </tr> ); })} </tbody> </table> </div> </Card.Body> </Card> </Col> {/* Horas Extras */} <Col md={12} className="mb-4"> <Card> <Card.Body> <h5>Reporte de Horas Extras</h5> <Row> <Col md={2}> <label>Desde</label> <DatePicker selected={extraHoursFilters.fromDate} onChange={(date) => setExtraHoursFilters((prev) => ({ ...prev, fromDate: date, })) } dateFormat="yyyy-MM-dd" /> </Col> <Col md={2}> <label>Hasta</label> <DatePicker selected={extraHoursFilters.toDate} onChange={(date) => setExtraHoursFilters((prev) => ({ ...prev, toDate: date, })) } dateFormat="yyyy-MM-dd" /> </Col> <Col md={4}> <Dropdown onSelect={(extra) => setExtraHoursFilters((prev) => ({ ...prev, extra, })) } > <Dropdown.Toggle> {extraHoursFilters.extra || "Filtrar por horas extra"} </Dropdown.Toggle> <Dropdown.Menu> <Dropdown.Item eventKey="">Sin Filtro</Dropdown.Item> <Dropdown.Item eventKey="yes">Con Horas Extra</Dropdown.Item> <Dropdown.Item eventKey="no">Sin Horas Extra</Dropdown.Item> </Dropdown.Menu> </Dropdown> </Col> <Col md={4} className="d-flex align-items-end"> <Button onClick={() => handleFilter("extra-hours", extraHoursFilters, setExtraHoursData) } > Filtrar </Button> <Button variant="secondary" className="ms-2" onClick={() => { setExtraHoursFilters({ extra: "", fromDate: null, toDate: null }); fetchData(); }} > Sin Filtro </Button> <Button variant="success" className="ms-2" onClick={() => downloadCSV(extraHoursData, "horas_extras.csv")} > CSV </Button> </Col> </Row> <div className="mt-3 table-responsive"> <table className="table table-striped"> <thead> <tr> <th>RUT</th> <th>Nombre Completo</th> <th>Fecha</th> <th>Horas Extras</th> </tr> </thead> <tbody> {extraHoursData.map((entry, index) => { const extraHoursDecimal = entry.extra_hours_worked ? ((entry.extra_hours_worked.hours || 0) + (entry.extra_hours_worked.minutes || 0) / 60 + (entry.extra_hours_worked.seconds || 0) / 3600) : 0; const fullName = getFullName(entry); return ( <tr key={index}> <td>{entry.employee_id}</td> <td>{fullName}</td> <td>{entry.work_date}</td> <td>{extraHoursDecimal.toFixed(2)}</td> </tr> ); })} </tbody> </table> </div> </Card.Body> </Card> </Col> </Row> </Container> ); }; export default ReportsTable;