Futuristic Dashboard - Copy this React, Tailwind Component to your project
User Profile Help me modify both returns in order to make the app the most futuristic and fancier possible, giving maximum ui ux experience: import React, { useState, useEffect } from "react"; import axios from "axios"; import { baseAPI } from "../config"; import CurrencyFormat from "react currency format"; import moment from "moment"; import Sidebar from "../partials/Sidebar"; import Header from "../partials/Header"; import Footer from "../partials/Footer"; import WelcomeBanner from "../partials/components/WelcomeBanner"; import ActionMenu from "../partials/actions/ActionsMenu"; import { EyeIcon, EyeSlashIcon, PencilIcon } from "@heroicons/react/20/solid"; // Función para aplicar estilos condicionales a Total Interacciones const getInteractionStyle = (totalInteractions) => { if (totalInteractions > 15) { return "bg red 100 text red 600"; // Fondo rojo claro y texto rojo } else if (totalInteractions >= 5) { return "bg green 100 text green 600"; // Fondo verde claro y texto verde } else { return "bg orange 100 text orange 600"; // Fondo naranja claro y texto naranja } }; const Interactions = ({ interaction, toggleIsDone }) => { console.log("Datos de interacción:", interaction); // Verifica el contenido de cada interacción const [showDetails, setShowDetails] = useState(false); const [visibleAnnotations, setVisibleAnnotations] = useState(10); const [visibleInteractions, setVisibleInteractions] = useState(10); const formattedDate = interaction.createdAt ? moment(interaction.createdAt).format("DD/MM/YYYY") : "No Date"; const importeDeudaFormatted = interaction.importe_deuda / 1; // Cargar más anotaciones const loadMoreAnnotations = () => { setVisibleAnnotations((prev) => prev + 10); }; // Cargar más interacciones const loadMoreInteractions = () => { setVisibleInteractions((prev) => prev + 10); }; return ( <> <tr> <td className="px 3 py 2 whitespace nowrap"> <div className="text left text gray 800">{formattedDate}</div> </td> <td className="px 3 py 2 whitespace nowrap"> <div className="text left text gray 800">{interaction.nombre_comercial}</div> </td> <td className="px 3 py 2 whitespace nowrap"> <div className="text left text gray 800"> {interaction.deudores && interaction.deudores.length > 0 ? interaction.deudores[0].debtorname : "Sin deudor"} </div> </td> <td className="px 3 py 2 whitespace nowrap"> <div className="text left text gray 800"> <CurrencyFormat value={importeDeudaFormatted} displayType={"text"} thousandSeparator={"."} decimalSeparator={","} suffix={" €"} decimalScale={2} /> </div> </td> <td className="px 3 py 2 w [70px] whitespace nowrap text center"> <div className={`text center font semibold px 2 py 1 rounded ${getInteractionStyle( interaction.total_interactions )}`} > {interaction.total_interactions} {interaction.total_interactions > 15 && " 🔥"} </div> </td> <td className="px 3 py 2 whitespace nowrap text center"> <label className="flex items center cursor pointer"> <input type="checkbox" checked={interaction.isDone === true} onChange={() => toggleIsDone(interaction.expedient_id, interaction.isDone)} className="hidden" /> <span className={`w 10 h 5 flex items center flex shrink 0 p 1 rounded full ${ interaction.isDone ? "bg green 400" : "bg gray 300" }`} > <span className={`w 4 h 4 bg white rounded full shadow transform transition transform ${ interaction.isDone ? "translate x 5" : "translate x 0" }`} ></span> </span> </label> </td> <td className="px 3 py 2 whitespace nowrap"> <div className="flex space x 2 items center"> <button className="text indigo 500 hover:text indigo 600" onClick={() => (window.location.href = `/view/${interaction.expedient_id}`) } > <PencilIcon className="h 4 w 4 sm:h 5 sm:w 5" aria hidden="true" /> </button> <button className="text indigo 500 hover:text indigo 600" onClick={() => setShowDetails(!showDetails)} > {showDetails ? ( <EyeSlashIcon className="h 4 w 4 sm:h 5 sm:w 5" aria hidden="true" /> ) : ( <EyeIcon className="h 4 w 4 sm:h 5 sm:w 5" aria hidden="true" /> )} </button> <ActionMenu expedientId={interaction.expedient_id} /> </div> </td> </tr> {showDetails && ( <tr> <td colSpan="5" className="bg gray 50"> <div className="p 4 border t border gray 200 rounded b lg space y 4"> {/* 📌 Anotaciones */} {interaction.annotations.length > 0 ? ( <> <h3 className="font semibold text gray 800"> 📌 Anotaciones ({interaction.annotations.length}) </h3> <ul className="list disc list inside text gray 700"> {interaction.annotations .sort((a, b) => new Date(b.fecha) new Date(a.fecha)) // 🔥 Ordenar de más reciente a más antiguo .slice(0, visibleAnnotations) .map((annotation) => ( <li key={annotation._id} className="text sm"> ✏️ <strong>{annotation.anotacion}</strong> ( {moment(annotation.fecha).format("DD/MM/YYYY")}) </li> ))} </ul> {visibleAnnotations < interaction.annotations.length && ( <button onClick={loadMoreAnnotations} className="mt 2 text indigo 600 hover:underline text sm" > Ver más anotaciones... </button> )} </> ) : ( <p className="text sm text gray 500">No hay anotaciones.</p> )} {/* 💬 Interacciones */} {interaction.interactions.length > 0 ? ( <> <h3 className="font semibold text gray 800"> 💬 Interacciones ({interaction.interactions.length}) </h3> <ul className="list disc list inside text gray 700 space y 2"> {interaction.interactions .sort((a, b) => new Date(b.date) new Date(a.date)) // 🔥 Ordenar de más reciente a más antiguo .slice(0, visibleInteractions) .map((interactionItem) => ( <li key={interactionItem.interaction_id} className={`text sm p 2 rounded md ${ interactionItem.direction === "outgoing" ? "bg blue 100 text blue 800" : "bg green 100 text green 800" }`} > <strong>{interactionItem.type}</strong> {" "} {moment(interactionItem.date).format("DD/MM/YYYY")} {" "} {interactionItem.details?.message || "Sin detalles"} </li> ))} </ul> {visibleInteractions < interaction.interactions.length && ( <button onClick={loadMoreInteractions} className="mt 2 text indigo 600 hover:underline text sm" > Ver más interacciones... </button> )} </> ) : ( <p className="text sm text gray 500">No hay interacciones.</p> )} </div> </td> </tr> )} </> ); }; const InteractionsList = () => { const [sidebarOpen, setSidebarOpen] = useState(false); const [interactions, setInteractions] = useState([]); // Estado para guardar las interacciones const [completedInteractions, setCompletedInteractions] = useState([]); const [loading, setLoading] = useState(true); // Estado para controlar la carga const [sortField, setSortField] = useState("createdAt"); // Campo para ordenar const [sortOrder, setSortOrder] = useState("asc"); // Orden asc o desc const [currentPageCompleted, setCurrentPageCompleted] = useState(1); const [totalPagesCompleted, setTotalPagesCompleted] = useState(1); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const itemsPerPage = 10; // Límite de elementos por página const [totalUnreadCount, setTotalUnreadCount] = useState(0); const [totalDoneCount, setTotalDoneCount] = useState(0); const userToken = localStorage.getItem("token"); if (userToken == null) { window.location = "/login"; } // Llamada a la API para obtener las interacciones useEffect(() => { const fetchInteractions = async () => { setLoading(true); try { const response = await axios.get( `${baseAPI}/expedients/unread interactions?page=${currentPage}&limit=${itemsPerPage}` ); console.log("Interacciones:", response.data.expedients); setInteractions(response.data.expedients); setTotalPages(response.data.totalPages); setTotalUnreadCount(response.data.totalUnreadCount); } catch (error) { console.error("Error fetching interactions", error); } finally { setLoading(false); } }; fetchInteractions(); }, [userToken, currentPage, itemsPerPage]); useEffect(() => { const fetchCompletedInteractions = async () => { setLoading(true); try { const response = await axios.get( `${baseAPI}/expedients/done interactions?page=${currentPageCompleted}&limit=${itemsPerPage}` ); console.log("Interacciones Completadas:", response.data.expedients); setCompletedInteractions(response.data.expedients); setTotalPagesCompleted(response.data.totalPages); setTotalDoneCount(response.data.totalDoneCount); } catch (error) { console.error("Error fetching completed interactions", error); } finally { setLoading(false); } }; fetchCompletedInteractions(); }, [userToken, currentPageCompleted, itemsPerPage]); const toggleIsDone = async (expedientId, currentState) => { try { await axios.post(`${baseAPI}/expedients/update isdone`, { expedient_id: expedientId, isDone: !currentState, }); // Refrescar la página window.location.reload(); } catch (error) { console.error("Error updating isDone", error); } }; // Función para ordenar las interacciones const sortInteractions = (field) => { const order = sortField === field && sortOrder === "asc" ? "desc" : "asc"; // Cambia el orden si ya está ordenado por el mismo campo setSortField(field); setSortOrder(order); const sortedInteractions = [...interactions].sort((a, b) => { let comparison = 0; if (field === "createdAt") { comparison = new Date(b.createdAt) new Date(a.createdAt); } else if (field === "importe_deuda") { comparison = a.importe_deuda b.importe_deuda; } else if (field === "total_interactions") { comparison = a.total_interactions b.total_interactions; } return order === "asc" ? comparison : comparison; }); setInteractions(sortedInteractions); }; return ( <div className="flex h screen overflow hidden"> {/* Sidebar */} <Sidebar sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} /> {/* Content area */} <div className="relative flex flex col flex 1 overflow y auto overflow x hidden"> {/* Site header */} <Header sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} /> <main> <div className="px 4 sm:px 6 lg:px 8 py 6 w full max w 9xl mx auto"> {/* Welcome banner */} <WelcomeBanner /> {/* Título de la tabla */} <header className="px 4 py 3 border b border gray 100 flex justify between items center"> <h2 className="font semibold text gray 800 text lg"> 📬 Interacciones Pendientes </h2> <div className="text sm text gray 600"> Pendientes: {totalUnreadCount} </div> </header> {/* Tabla de interacciones */} <div className="grid grid cols 12 gap 4 mt 4"> {loading ? ( <p className="text center col span 12">Cargando...</p> ) : ( <div className="col span 12"> <div className="overflow x auto"> <table className="table auto w full text left"> <thead className="text xs font semibold uppercase text gray 600 bg gray 100"> <tr> <th className="px 3 py 2 cursor pointer" onClick={() => sortInteractions("createdAt")} > <div className="flex items center"> Fecha Creación{" "} {sortField === "createdAt" && (sortOrder === "asc" ? "🔼" : "🔽")} </div> </th> <th className="px 3 py 2">Cliente</th> <th className="px 3 py 2">Deudor</th> <th className="px 3 py 2 cursor pointer" onClick={() => sortInteractions("importe_deuda")} > <div className="flex items center"> Importe Deuda{" "} {sortField === "importe_deuda" && (sortOrder === "asc" ? "🔼" : "🔽")} </div> </th> <th className="px 3 py 2 cursor pointer" onClick={() => sortInteractions("total_interactions") } > <div className="flex items center"> Interacciones{" "} {sortField === "total_interactions" && (sortOrder === "asc" ? "🔼" : "🔽")} </div> </th> <th className="px 3 py 2">Estado</th> <th className="px 3 py 2">Acciones</th> </tr> </thead> <tbody className="text sm divide y divide gray 200"> {interactions.map((interaction) => ( <Interactions key={interaction.expedient_id} interaction={interaction} toggleIsDone={toggleIsDone} /> ))} </tbody> </table> {interactions.length === 0 && ( <div className="text center text gray 400 py 6"> No hay interacciones registradas. </div> )} </div> </div> )} </div> <div className="flex justify center items center mt 4 space x 2"> <button onClick={() => setCurrentPage((prev) => Math.max(prev 1, 1))} disabled={currentPage === 1} className={`px 4 py 2 rounded ${ currentPage === 1 ? "bg gray 300 cursor not allowed" : "bg indigo 500 text white" }`} > Anterior </button> <span className="text gray 700"> Página {currentPage} de {totalPages} </span> <button onClick={() => setCurrentPage((prev) => Math.min(prev + 1, totalPages))} disabled={currentPage === totalPages} className={`px 4 py 2 rounded ${ currentPage === totalPages ? "bg gray 300 cursor not allowed" : "bg indigo 500 text white" }`} > Siguiente </button> </div> </div> <header className="px 4 py 3 border b border gray 100 flex justify between items center mt 10"> <h2 className="font semibold text gray 800 text lg"> ✅ Interacciones Completadas </h2> <div className="text sm text gray 600"> Completadas:{totalDoneCount}{" "} </div> </header> {/* Tabla de interacciones leidas */} <div className="grid grid cols 12 gap 4 mt 4"> {loading ? ( <p className="text center col span 12">Cargando...</p> ) : ( <div className="col span 12"> <div className="overflow x auto"> <table className="table auto w full text left"> <thead className="text xs font semibold uppercase text gray 600 bg gray 100"> <tr> <th className="px 3 py 2 cursor pointer" onClick={() => sortInteractions("createdAt")} > <div className="flex items center"> Fecha Creación{" "} {sortField === "createdAt" && (sortOrder === "asc" ? "🔼" : "🔽")} </div> </th> <th className="px 3 py 2">Cliente</th> <th className="px 3 py 2">Deudor</th> <th className="px 3 py 2 cursor pointer" onClick={() => sortInteractions("importe_deuda")} > <div className="flex items center"> Importe Deuda{" "} {sortField === "importe_deuda" && (sortOrder === "asc" ? "🔼" : "🔽")} </div> </th> <th className="px 3 py 2 cursor pointer" onClick={() => sortInteractions("total_interactions") } > <div className="flex items center"> Interacciones{" "} {sortField === "total_interactions" && (sortOrder === "asc" ? "🔼" : "🔽")} </div> </th> <th className="px 3 py 2">Estado</th> <th className="px 3 py 2">Acciones</th> </tr> </thead> <tbody className="text sm divide y divide gray 200"> {completedInteractions.map((interaction) => ( <Interactions key={interaction.expedient_id} interaction={interaction} toggleIsDone={toggleIsDone} /> ))} </tbody> </table> {interactions.length === 0 && ( <div className="text center text gray 400 py 6"> No hay interacciones registradas. </div> )} </div> </div> )} </div> <div className="flex justify center items center mt 4 space x 2"> <button onClick={() => setCurrentPageCompleted((prev) => Math.max(prev 1, 1))} disabled={currentPageCompleted === 1} className={`px 4 py 2 rounded ${ currentPageCompleted === 1 ? "bg gray 300 cursor not allowed" : "bg green 500 text white" }`} > Anterior </button> <span className="text gray 700"> Página {currentPageCompleted} de {totalPagesCompleted} </span> <button onClick={() => setCurrentPageCompleted((prev) => Math.min(prev + 1, totalPagesCompleted))} disabled={currentPageCompleted === totalPagesCompleted} className={`px 4 py 2 rounded ${ currentPageCompleted === totalPagesCompleted ? "bg gray 300 cursor not allowed" : "bg green 500 text white" }`} > Siguiente </button> </div> </main> <Footer /> </div> </div> ); }; export default InteractionsList;
