import React from 'react'; import { Container, Typography, Paper, Box, Divider, Grid, Card, CardContent, List, ListItem, ListItemText, Tabs, Tab } from '@mui/material'; import { Navigate, Link } from 'react-router-dom'; import PersonIcon from '@mui/icons-material/Person'; import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; import BarChartIcon from '@mui/icons-material/BarChart'; import { Chart as ChartJS, CategoryScale, LinearScale, LineElement, PointElement, Title, Tooltip, Legend, } from 'chart.js'; import { Line } from 'react-chartjs-2'; import { ADMIN_COLORS, getAdminStyles } from '../theme/adminColors.js'; ChartJS.register( CategoryScale, LinearScale, LineElement, PointElement, Title, Tooltip, Legend ); class AdminPage extends React.Component { constructor(props) { super(props); this.state = { users: {}, user: null, stats: null, loading: true, redirect: false }; } checkUserLoggedIn = () => { const storedUser = sessionStorage.getItem('user'); if (!storedUser) { this.setState({ redirect: true, user: null }); return; } try { const userData = JSON.parse(storedUser); if (!userData) { this.setState({ redirect: true, user: null }); } else if (!this.state.user) { // Only update user if it's not already set this.setState({ user: userData, loading: false }); } } catch (error) { console.error('Error parsing user from sessionStorage:', error); this.setState({ redirect: true, user: null }); } // Once loading is complete if (this.state.loading) { this.setState({ loading: false }); } } handleStorageChange = (e) => { if (e.key === 'user' && !e.newValue) { // User was removed from sessionStorage in another tab this.setState({ redirect: true, user: null }); } } handleCartUpdated = (id,user,cart,id2) => { const users = this.state.users; if(user && user.email) id = user.email; if(id2) id=id2; if(cart) users[id] = cart; if(!users[id]) delete users[id]; console.log(users); this.setState({ users }); } componentDidMount() { this.loadInitialData(); this.addSocketListeners(); this.checkUserLoggedIn(); // Set up interval to regularly check login status this.checkLoginInterval = setInterval(this.checkUserLoggedIn, 1000); // Add storage event listener to detect when user logs out in other tabs window.addEventListener('storage', this.handleStorageChange); } componentDidUpdate(prevProps) { // Handle socket connection changes const wasConnected = prevProps.socket && prevProps.socket.connected; const isNowConnected = this.props.socket && this.props.socket.connected; if (!wasConnected && isNowConnected) { // Socket just connected, add listeners and reload data this.addSocketListeners(); this.loadInitialData(); } else if (wasConnected && !isNowConnected) { // Socket just disconnected, remove listeners this.removeSocketListeners(); } } componentWillUnmount() { this.removeSocketListeners(); // Clear interval and remove event listeners if (this.checkLoginInterval) { clearInterval(this.checkLoginInterval); } window.removeEventListener('storage', this.handleStorageChange); } loadInitialData = () => { if (this.props.socket && this.props.socket.connected) { this.props.socket.emit('getStats', (stats) => { console.log('AdminPage: getStats', JSON.stringify(stats,null,2)); this.setState({stats: stats}); }); this.props.socket.emit('initialCarts', (carts) => { console.log('AdminPage: initialCarts', carts); if(carts && carts.success == true) { const users = {}; for(const item of carts.carts){ const user = {email:item.email}; let id = item.clientUrlId || item.socketId; const cart = item.cart; if(user && user.email) id = user.email; if(cart) users[id] = cart; } this.setState({ users: users }); } }); } } addSocketListeners = () => { if (this.props.socket && this.props.socket.connected) { // Remove existing listeners first to avoid duplicates this.removeSocketListeners(); this.props.socket.on('cartUpdated', this.handleCartUpdated); } } removeSocketListeners = () => { if (this.props.socket) { this.props.socket.off('cartUpdated', this.handleCartUpdated); } } formatPrice = (price) => { return typeof price === 'number' ? `€${price.toFixed(2)}` : price; } prepareChartData = () => { if (!this.state.stats || !this.state.stats.data || !this.state.stats.data.last30Days) { return null; } const dailyData = this.state.stats.data.last30Days.dailyData || []; // Sort data by date to ensure proper chronological order const sortedData = [...dailyData].sort((a, b) => new Date(a.date) - new Date(b.date)); const labels = sortedData.map(item => { const date = new Date(item.date); return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); }); const socketConnections = sortedData.map(item => item.socket_connections || 0); const productViewCalls = sortedData.map(item => item.get_product_view_calls || 0); return { labels, socketConnections, productViewCalls }; } getSocketConnectionsChartData = () => { const data = this.prepareChartData(); if (!data) return null; return { labels: data.labels, datasets: [ { label: 'Site Visits', data: data.socketConnections, borderColor: '#8be9fd', // terminal.ansiCyan backgroundColor: 'rgba(139, 233, 253, 0.2)', // terminal.ansiCyan with transparency tension: 0.1, pointBackgroundColor: '#8be9fd', pointBorderColor: '#8be9fd', }, ], }; } getProductViewCallsChartData = () => { const data = this.prepareChartData(); if (!data) return null; return { labels: data.labels, datasets: [ { label: 'Product Detail Page Visits', data: data.productViewCalls, backgroundColor: 'rgba(255, 121, 198, 0.2)', // terminal.ansiMagenta with transparency borderColor: '#ff79c6', // terminal.ansiMagenta borderWidth: 2, tension: 0.1, pointBackgroundColor: '#ff79c6', pointBorderColor: '#ff79c6', }, ], }; } getChartOptions = (title) => ({ responsive: true, plugins: { legend: { position: 'top', labels: { color: ADMIN_COLORS.primaryText, font: { family: ADMIN_COLORS.fontFamily } } }, title: { display: true, text: title, color: ADMIN_COLORS.primary, font: { family: ADMIN_COLORS.fontFamily, weight: 'bold' } }, }, scales: { x: { beginAtZero: true, ticks: { color: ADMIN_COLORS.primaryText, font: { family: ADMIN_COLORS.fontFamily } }, grid: { color: ADMIN_COLORS.border } }, y: { beginAtZero: true, ticks: { color: ADMIN_COLORS.primaryText, font: { family: ADMIN_COLORS.fontFamily } }, grid: { color: ADMIN_COLORS.border } }, }, backgroundColor: ADMIN_COLORS.surfaceBackground, color: ADMIN_COLORS.primaryText }) render() { const { users } = this.state; if (this.state.redirect || (!this.state.loading && !this.state.user)) { return ; } // Check if current user is admin if (this.state.user && !this.state.user.admin) { return ; } const hasUsers = Object.keys(users).length > 0; const socketConnectionsData = this.getSocketConnectionsChartData(); const productViewCallsData = this.getProductViewCallsChartData(); const styles = getAdminStyles(); return ( {/* Admin Navigation Tabs */} {/* Analytics Charts Section */} {(socketConnectionsData || productViewCallsData) && ( 30-Day Analytics {socketConnectionsData && ( )} {productViewCallsData && ( )} )} Active User Carts {!hasUsers && ( No active user carts at the moment. )} {hasUsers && ( {Object.keys(users).map((user, i) => { const cartItems = Object.keys(users[user]); const totalValue = cartItems.reduce((total, item) => { return total + (parseFloat(users[user][item].price) || 0); }, 0); return ( {user} {cartItems.map((item, j) => ( ))} Total: {this.formatPrice(totalValue)} ); })} )} ); } } export default AdminPage;