import React, { useState, useEffect, useRef } from 'react';
import {
CContainer, CRow, CCol, CCard, CCardBody, CCardHeader,
CFormInput, CButton, CTable, CNavbar, CNavbarBrand,
CNav, CNavItem, CForm, CBadge, CModal, CModalHeader,
CModalTitle, CModalBody, CModalFooter, CFormSwitch,
COffcanvas, COffcanvasHeader, COffcanvasTitle, COffcanvasBody, CCloseButton,
CTooltip
} from '@coreui/react';
// --- INLINE ICONS ---
const IconEye = () => ();
const IconEdit = () => ();
const IconTrash = () => ();
const IconDrag = () => ();
function App() {
const [userRole, setUserRole] = useState(null);
const [theme, setTheme] = useState('light');
// Mobile & Sidebar State
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
const [sidebarVisible, setSidebarVisible] = useState(false);
const [desktopSidebarVisible, setDesktopSidebarVisible] = useState(true);
// System Alert Modal State
const [alertModalVisible, setAlertModalVisible] = useState(false);
const [alertModalTitle, setAlertModalTitle] = useState('');
const [alertModalMessage, setAlertModalMessage] = useState('');
// Login State
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
// Admin User Management State
const [users, setUsers] = useState([]);
const [selectedAdminUserId, setSelectedAdminUserId] = useState(null); // Tracks the active user being edited
const [newUsername, setNewUsername] = useState('');
const [newPassword, setNewPassword] = useState('');
const [newReportTitle, setNewReportTitle] = useState('');
const [newDesktopUrl, setNewDesktopUrl] = useState('');
const [newMobileUrl, setNewMobileUrl] = useState('');
// Derived active user for the admin dashboard panel
const activeAdminUser = users.find(u => u.id === selectedAdminUserId) || null;
// Admin Manage Reports State
const [reportModalVisible, setReportModalVisible] = useState(false);
const [reportModalMode, setReportModalMode] = useState('add');
const [activeUserForModal, setActiveUserForModal] = useState(null);
const [activeReportId, setActiveReportId] = useState(null);
const [modalReportTitle, setModalReportTitle] = useState('');
const [modalDesktopUrl, setModalDesktopUrl] = useState('');
const [modalMobileUrl, setModalMobileUrl] = useState('');
// Admin Reset Password State
const [passwordModalVisible, setPasswordModalVisible] = useState(false);
const [newPasswordInput, setNewPasswordInput] = useState('');
// Viewer State
const [pages, setPages] = useState([]);
const [selectedPage, setSelectedPage] = useState(null);
// Force Refresh State & Ref
const [refreshKey, setRefreshKey] = useState(0);
const [lastRefresh, setLastRefresh] = useState('');
const lastAutoRefreshHour = useRef(null);
// Drag and Drop Refs
const dragItem = useRef();
const dragOverItem = useRef();
const dragUser = useRef();
useEffect(() => {
document.documentElement.setAttribute('data-coreui-theme', theme);
}, [theme]);
useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth < 768);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// Ensures there is always a selected user in the admin panel if users exist
useEffect(() => {
if (userRole === 'admin' && users.length > 0) {
if (!selectedAdminUserId || !users.find(u => u.id === selectedAdminUserId)) {
setSelectedAdminUserId(users[0].id);
}
}
}, [users, userRole, selectedAdminUserId]);
useEffect(() => {
if (selectedPage && userRole === 'user') {
setLastRefresh(new Date().toLocaleTimeString());
setRefreshKey(prev => prev + 1);
}
}, [selectedPage, userRole]);
useEffect(() => {
const checkTimeInterval = setInterval(() => {
if (!selectedPage || userRole !== 'user') return;
const now = new Date();
const currentHour = now.getHours();
const currentMinute = now.getMinutes();
if (currentHour >= 7 && currentHour <= 15 && currentMinute === 5) {
if (lastAutoRefreshHour.current !== currentHour) {
setRefreshKey(prev => prev + 1);
setLastRefresh(`Auto-update: ${now.toLocaleTimeString()}`);
lastAutoRefreshHour.current = currentHour;
}
}
}, 30000);
return () => clearInterval(checkTimeInterval);
}, [selectedPage, userRole]);
const showSystemAlert = (title, message) => {
setAlertModalTitle(title);
setAlertModalMessage(message);
setAlertModalVisible(true);
};
useEffect(() => {
let sessionTimer;
if (userRole) {
const EIGHT_HOURS = 8 * 60 * 60 * 1000;
sessionTimer = setTimeout(() => {
handleLogout();
showSystemAlert("Session Expired", "Your 8-hour session has automatically ended. Please log in again to continue viewing reports.");
}, EIGHT_HOURS);
}
return () => clearTimeout(sessionTimer);
}, [userRole]);
const toggleTheme = () => setTheme(theme === 'light' ? 'dark' : 'light');
const handleManualRefresh = () => {
setRefreshKey(prev => prev + 1);
setLastRefresh(new Date().toLocaleTimeString());
};
const handleLogin = async (e) => {
e.preventDefault();
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
if (res.ok) {
const data = await res.json();
setUserRole(data.role);
if (data.role === 'admin') fetchAdminUsers();
if (data.role === 'user') loadUserPages();
} else {
showSystemAlert("Login Failed", "Invalid username or password. Please check your credentials and try again.");
}
};
const handleLogout = async () => {
await fetch('/api/logout', { method: 'POST' });
setUserRole(null);
setPages([]);
setSelectedPage(null);
setSelectedAdminUserId(null);
};
const fetchAdminUsers = async () => {
const res = await fetch('/api/admin/users');
if (res.status === 401 || res.status === 403) return handleLogout();
if (res.ok) {
const data = await res.json();
setUsers(data);
}
};
const handleAddUser = async (e) => {
e.preventDefault();
const initialPages = [];
if (newReportTitle && newDesktopUrl) {
initialPages.push({
id: 'rep_' + Date.now(),
title: newReportTitle,
desktopUrl: newDesktopUrl,
mobileUrl: newMobileUrl
});
}
const newUser = {
username: newUsername,
password: newPassword,
pages: initialPages
};
const res = await fetch('/api/admin/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser)
});
if (res.status === 401 || res.status === 403) return handleLogout();
if (res.ok) {
const createdUser = await res.json();
fetchAdminUsers();
setSelectedAdminUserId(createdUser.id); // Auto-select the newly created user
setNewUsername('');
setNewPassword('');
setNewReportTitle('');
setNewDesktopUrl('');
setNewMobileUrl('');
}
};
const handleDeleteUser = async (id) => {
const res = await fetch(`/api/admin/users/${id}`, { method: 'DELETE' });
if (res.status === 401 || res.status === 403) return handleLogout();
fetchAdminUsers(); // This will auto-select the next available user via the useEffect
};
const openReportModal = (mode, user, report = null) => {
setReportModalMode(mode);
setActiveUserForModal(user);
if (mode === 'edit' && report) {
setActiveReportId(report.id);
setModalReportTitle(report.title);
setModalDesktopUrl(report.desktopUrl || report.url || '');
setModalMobileUrl(report.mobileUrl || '');
} else {
setActiveReportId(null);
setModalReportTitle('');
setModalDesktopUrl('');
setModalMobileUrl('');
}
setReportModalVisible(true);
};
const handleSaveReport = async () => {
try {
const currentUser = users.find(u => u.id === activeUserForModal.id);
if (!currentUser) return showSystemAlert("Error", "User record not found in active state.");
let updatedPages = [...currentUser.pages];
if (reportModalMode === 'add') {
updatedPages.push({
id: 'rep_' + Date.now(),
title: modalReportTitle,
desktopUrl: modalDesktopUrl,
mobileUrl: modalMobileUrl
});
} else if (reportModalMode === 'edit') {
updatedPages = updatedPages.map(p =>
p.id === activeReportId ? { ...p, title: modalReportTitle, desktopUrl: modalDesktopUrl, mobileUrl: modalMobileUrl } : p
);
}
const res = await fetch(`/api/admin/users/${currentUser.id}/pages`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pages: updatedPages })
});
if (res.status === 401 || res.status === 403) return handleLogout();
if (res.ok) {
setReportModalVisible(false);
fetchAdminUsers();
} else {
showSystemAlert("Save Failed", "The server failed to save the report updates.");
}
} catch (error) {
showSystemAlert("Error", "An unexpected error occurred during save.");
}
};
const handleDeleteReport = async (user, reportId) => {
const updatedPages = user.pages.filter(p => p.id !== reportId);
const res = await fetch(`/api/admin/users/${user.id}/pages`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pages: updatedPages })
});
if (res.status === 401 || res.status === 403) return handleLogout();
fetchAdminUsers();
};
const openPasswordModal = (user) => {
setActiveUserForModal(user);
setNewPasswordInput('');
setPasswordModalVisible(true);
};
const handleSavePassword = async () => {
const res = await fetch(`/api/admin/users/${activeUserForModal.id}/password`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: newPasswordInput })
});
if (res.status === 401 || res.status === 403) return handleLogout();
if (res.ok) {
setPasswordModalVisible(false);
fetchAdminUsers();
}
};
const loadUserPages = async () => {
const res = await fetch('/api/user/pages');
if (res.status === 401 || res.status === 403) return handleLogout();
if (res.ok) {
const data = await res.json();
setPages(data);
if(data.length > 0) setSelectedPage(data[0]);
}
};
// --- DRAG AND DROP HANDLERS ---
const handleDragStart = (e, userId, index) => {
dragItem.current = index;
dragUser.current = userId;
e.dataTransfer.effectAllowed = 'move';
setTimeout(() => e.target.style.opacity = '0.5', 0);
};
const handleDragEnter = (e, userId, index) => {
if (dragUser.current !== userId) return;
dragOverItem.current = index;
};
const handleDragEnd = async (e, userId) => {
e.target.style.opacity = '1';
if (dragUser.current !== userId || dragItem.current === null || dragOverItem.current === null) return;
if (dragItem.current === dragOverItem.current) return;
const currentUser = users.find(u => u.id === userId);
if (!currentUser) return;
const newPages = [...currentUser.pages];
const draggedItemContent = newPages[dragItem.current];
newPages.splice(dragItem.current, 1);
newPages.splice(dragOverItem.current, 0, draggedItemContent);
// Optimistic UI Update ensures no visual lag
setUsers(users.map(u => u.id === userId ? { ...u, pages: newPages } : u));
const res = await fetch(`/api/admin/users/${userId}/pages`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pages: newPages })
});
if (res.status === 401 || res.status === 403) return handleLogout();
dragItem.current = null;
dragOverItem.current = null;
dragUser.current = null;
};
// --- DYNAMIC OVERLAY STYLES ---
const overlayStyleLeft = {
position: 'absolute', bottom: 0, left: 0,
width: '140px',
height: isMobile ? '30px' : '34px',
backgroundColor: '#eaeaea', zIndex: 10
};
const overlayStyleRight = {
position: 'absolute', bottom: 0, right: 0,
width: isMobile ? '30px' : '190px',
height: isMobile ? '30px' : '34px',
backgroundColor: '#eaeaea', zIndex: 10
};
const getActiveUrl = (page) => {
if (!page) return '';
if (isMobile && page.mobileUrl) return page.mobileUrl;
return page.desktopUrl || page.url;
};
const renderAlertModal = () => (
LOGIN
No users created yet.
} {users.map(u => ({activeAdminUser.password}This user currently has no reports assigned.
} {activeAdminUser.pages.map((p, index) => (No reports assigned.
} {pages.map(page => (