625 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			625 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable file
		
	
	
	
	
<?php
 | 
						|
require_once __DIR__ . "/session_bootstrap.php";
 | 
						|
require_once "functions.php";
 | 
						|
 | 
						|
// Set a timestamp for when the page was loaded
 | 
						|
$page_load_time = time();
 | 
						|
 | 
						|
// Check if this is an AJAX request for additional data
 | 
						|
$is_ajax = isset($_GET["ajax"]) && $_GET["ajax"] === "1";
 | 
						|
 | 
						|
// Initialize job data array for progressive loading
 | 
						|
$all_jobs = [];
 | 
						|
$character_ids = [];
 | 
						|
 | 
						|
// Refresh tokens only when necessary
 | 
						|
if (isset($_SESSION["characters"]) && !empty($_SESSION["characters"])) {
 | 
						|
    // Only refresh tokens once every 5 minutes per user session
 | 
						|
    $last_token_refresh = $_SESSION["last_token_refresh"] ?? 0;
 | 
						|
    if ($page_load_time - $last_token_refresh > 300) {
 | 
						|
        foreach ($_SESSION["characters"] as $character_id => &$charData) {
 | 
						|
            if (isset($charData["refresh_token"])) {
 | 
						|
                // Check if the access token is expired or missing
 | 
						|
                if (
 | 
						|
                    !isset($charData["access_token"]) ||
 | 
						|
                    is_token_expired($charData["access_token"])
 | 
						|
                ) {
 | 
						|
                    $new_tokens = refresh_token($charData["refresh_token"]);
 | 
						|
                    if (!empty($new_tokens["access_token"])) {
 | 
						|
                        // Update session with new access token
 | 
						|
                        $charData["access_token"] = $new_tokens["access_token"];
 | 
						|
                    } else {
 | 
						|
                        error_log(
 | 
						|
                            "Failed to refresh token for character ID $character_id"
 | 
						|
                        );
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        unset($charData); // Break reference to avoid unexpected behavior
 | 
						|
        $_SESSION["last_token_refresh"] = $page_load_time;
 | 
						|
    }
 | 
						|
 | 
						|
    // Get list of character IDs
 | 
						|
    $character_ids = array_keys($_SESSION["characters"]);
 | 
						|
}
 | 
						|
 | 
						|
if (!isset($_SESSION["characters"]) || empty($_SESSION["characters"])) {
 | 
						|
    echo "No characters logged in. <a href='index.php'>Go back</a>";
 | 
						|
    exit();
 | 
						|
}
 | 
						|
 | 
						|
// For AJAX requests, only fetch requested character data and return JSON
 | 
						|
if ($is_ajax) {
 | 
						|
    $requested_char_id = isset($_GET["character_id"])
 | 
						|
        ? $_GET["character_id"]
 | 
						|
        : null;
 | 
						|
 | 
						|
    if (
 | 
						|
        $requested_char_id &&
 | 
						|
        isset($_SESSION["characters"][$requested_char_id])
 | 
						|
    ) {
 | 
						|
        $charData = $_SESSION["characters"][$requested_char_id];
 | 
						|
        $access_token = $charData["access_token"] ?? null;
 | 
						|
 | 
						|
        if ($access_token) {
 | 
						|
            $jobs = fetch_character_jobs($requested_char_id, $access_token);
 | 
						|
 | 
						|
            // Format jobs for JSON response
 | 
						|
            $formatted_jobs = [];
 | 
						|
            foreach ($jobs as $job) {
 | 
						|
                $end_timestamp = is_numeric($job["end_time_unix"])
 | 
						|
                    ? $job["end_time_unix"]
 | 
						|
                    : 0;
 | 
						|
                $formatted_jobs[] = [
 | 
						|
                    "character" => htmlspecialchars($job["character"]),
 | 
						|
                    "blueprint" => htmlspecialchars($job["blueprint"]),
 | 
						|
                    "activity" => htmlspecialchars($job["activity"]),
 | 
						|
                    "location" => htmlspecialchars($job["location"]),
 | 
						|
                    "start_time" => htmlspecialchars($job["start_time"]),
 | 
						|
                    "end_time" => htmlspecialchars($job["end_time"]),
 | 
						|
                    "end_time_unix" => $end_timestamp,
 | 
						|
                    "time_left" => htmlspecialchars($job["time_left"]),
 | 
						|
                ];
 | 
						|
            }
 | 
						|
 | 
						|
            header("Content-Type: application/json");
 | 
						|
            echo json_encode(["jobs" => $formatted_jobs]);
 | 
						|
            exit();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // If we get here, something went wrong
 | 
						|
    header("HTTP/1.1 400 Bad Request");
 | 
						|
    echo json_encode(["error" => "Invalid request"]);
 | 
						|
    exit();
 | 
						|
}
 | 
						|
?>
 | 
						|
 | 
						|
 | 
						|
<!DOCTYPE html>
 | 
						|
<html lang="en">
 | 
						|
<head>
 | 
						|
    <meta charset="UTF-8">
 | 
						|
    <title>Industry Jobs Dashboard</title>
 | 
						|
    <style>
 | 
						|
        th.sorted-asc::after { content: " ▲"; }
 | 
						|
        th.sorted-desc::after { content: " ▼"; }
 | 
						|
 | 
						|
        @keyframes spin {
 | 
						|
            0% { transform: rotate(0deg); }
 | 
						|
            100% { transform: rotate(360deg); }
 | 
						|
        }
 | 
						|
        .loading-row td {
 | 
						|
            text-align: center;
 | 
						|
            padding: 20px;
 | 
						|
            background-color: #1e1e1e;
 | 
						|
            color: #d4d4d4;
 | 
						|
            border-bottom: 1px solid #333;
 | 
						|
        }
 | 
						|
    </style>
 | 
						|
</head>
 | 
						|
<body>
 | 
						|
    <h1>Industry Jobs</h1>
 | 
						|
 | 
						|
    <table id="jobsTable">
 | 
						|
        <thead>
 | 
						|
            <tr>
 | 
						|
                <th>Character</th>
 | 
						|
                <th>Blueprint</th>
 | 
						|
                <th>Activity</th>
 | 
						|
                <th>Location</th>
 | 
						|
                <th>Start Time</th>
 | 
						|
                <th>End Time</th>
 | 
						|
                <th>Time Left</th>
 | 
						|
            </tr>
 | 
						|
        </thead>
 | 
						|
        <tbody>
 | 
						|
            <?php // Load data for the first character only (for fast initial load)
 | 
						|
 | 
						|
if (!empty($character_ids)) {
 | 
						|
                $first_char_id = $character_ids[0];
 | 
						|
                $charData = $_SESSION["characters"][$first_char_id];
 | 
						|
                $access_token = $charData["access_token"] ?? null;
 | 
						|
 | 
						|
                if ($access_token) {
 | 
						|
                    $jobs = fetch_character_jobs($first_char_id, $access_token);
 | 
						|
 | 
						|
                    if (empty($jobs)) {
 | 
						|
                        echo "<table class='no-jobs-table'><tr><td>No jobs found for " . htmlspecialchars($charData["name"]) . "</td></tr></table>";
 | 
						|
                    } else {
 | 
						|
                        foreach ($jobs as $job) {
 | 
						|
                            $end_timestamp = is_numeric($job["end_time_unix"])
 | 
						|
                                ? $job["end_time_unix"]
 | 
						|
                                : 0;
 | 
						|
                            $time_left_display = htmlspecialchars(
 | 
						|
                                $job["time_left"]
 | 
						|
                            );
 | 
						|
 | 
						|
                            echo "<tr>";
 | 
						|
                            echo "<td>" .
 | 
						|
                                htmlspecialchars($job["character"]) .
 | 
						|
                                "</td>";
 | 
						|
                            echo "<td>" .
 | 
						|
                                htmlspecialchars($job["blueprint"]) .
 | 
						|
                                "</td>";
 | 
						|
                            echo "<td>" .
 | 
						|
                                htmlspecialchars($job["activity"]) .
 | 
						|
                                "</td>";
 | 
						|
                            echo "<td>" .
 | 
						|
                                htmlspecialchars($job["location"]) .
 | 
						|
                                "</td>";
 | 
						|
                            echo "<td>" .
 | 
						|
                                htmlspecialchars($job["start_time"]) .
 | 
						|
                                "</td>";
 | 
						|
                            echo "<td>" .
 | 
						|
                                htmlspecialchars($job["end_time"]) .
 | 
						|
                                "</td>";
 | 
						|
                            echo "<td class='time-left' data-endtime='$end_timestamp'>" .
 | 
						|
                                $time_left_display .
 | 
						|
                                "</td>";
 | 
						|
                            echo "</tr>";
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                // Add loading placeholder rows for other characters
 | 
						|
                if (count($character_ids) > 1) {
 | 
						|
                    foreach (array_slice($character_ids, 1) as $char_id) {
 | 
						|
                        $char_name =
 | 
						|
                            $_SESSION["characters"][$char_id]["name"] ??
 | 
						|
                            "Character";
 | 
						|
                        echo "<tr id='loading-$char_id' class='loading-row'>";
 | 
						|
                        echo "<td colspan='7'>Loading jobs for " .
 | 
						|
                            htmlspecialchars($char_name) .
 | 
						|
                            " <div class='loader'></div></td>";
 | 
						|
                        echo "</tr>";
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            } ?>
 | 
						|
        </tbody>
 | 
						|
    </table>
 | 
						|
    <!-- List of all logged-in toons -->
 | 
						|
    <div class="character-list">
 | 
						|
        <h2>Logged-in Characters</h2>
 | 
						|
        <table id="jobsTable">
 | 
						|
            <thead>
 | 
						|
                <tr>
 | 
						|
                    <th>Name</th>
 | 
						|
                    <th>Character ID</th>
 | 
						|
                </tr>
 | 
						|
            </thead>
 | 
						|
            <tbody>
 | 
						|
                <?php foreach (
 | 
						|
                    $_SESSION["characters"]
 | 
						|
                    as $character_id => $charData
 | 
						|
                ): ?>
 | 
						|
                    <tr>
 | 
						|
                        <td><?php echo htmlspecialchars(
 | 
						|
                            $charData["name"]
 | 
						|
                        ); ?></td>
 | 
						|
                        <td><?php echo $character_id; ?></td>
 | 
						|
                    </tr>
 | 
						|
                <?php endforeach; ?>
 | 
						|
            </tbody>
 | 
						|
        </table>
 | 
						|
    </div>
 | 
						|
    <script>
 | 
						|
        // Track page visibility to handle inactive tabs
 | 
						|
        let pageVisible = true;
 | 
						|
        document.addEventListener("visibilitychange", () => {
 | 
						|
            pageVisible = document.visibilityState === "visible";
 | 
						|
            if (pageVisible) {
 | 
						|
                // Force a data refresh if the page has been hidden for a while
 | 
						|
                const lastRefresh = localStorage.getItem('lastDashboardRefresh');
 | 
						|
                const now = Date.now();
 | 
						|
                if (lastRefresh && (now - parseInt(lastRefresh) > 1800000)) { // 30 minutes
 | 
						|
                    window.location.reload();
 | 
						|
                }
 | 
						|
            }
 | 
						|
        });
 | 
						|
 | 
						|
        // Store the last refresh time
 | 
						|
        localStorage.setItem('lastDashboardRefresh', Date.now().toString());
 | 
						|
 | 
						|
        document.addEventListener("DOMContentLoaded", () => {
 | 
						|
            let sortDirection = [];
 | 
						|
            let animationFrameId = null;
 | 
						|
            let jobsLoading = true;
 | 
						|
            let lastSortedCol = null;
 | 
						|
            let lastSortedDir = null;
 | 
						|
 | 
						|
            // Jobs Table Sorting (disabled while loading)
 | 
						|
            const jobsTableHead = document.getElementById('jobsTable').querySelector('thead');
 | 
						|
            jobsTableHead.addEventListener('click', (event) => {
 | 
						|
                if (jobsLoading) {
 | 
						|
                    // Optionally show a tooltip or visual cue here
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
                const th = event.target;
 | 
						|
                if (th.tagName === 'TH') {
 | 
						|
                    const col = Array.from(th.parentNode.children).indexOf(th);
 | 
						|
                    sortTable('jobsTable', col);
 | 
						|
                    lastSortedCol = col;
 | 
						|
                    lastSortedDir = sortDirection[col];
 | 
						|
                }
 | 
						|
            });
 | 
						|
            // Visual cue: change cursor to not-allowed while loading
 | 
						|
            jobsTableHead.style.cursor = "not-allowed";
 | 
						|
 | 
						|
            // Character Table Sorting
 | 
						|
            const characterTable = document.getElementById('characterTable');
 | 
						|
            if (characterTable) {
 | 
						|
                characterTable.querySelector('thead').addEventListener('click', (event) => {
 | 
						|
                    const th = event.target;
 | 
						|
                    if (th.tagName === 'TH') {
 | 
						|
                        const col = Array.from(th.parentNode.children).indexOf(th);
 | 
						|
                        sortTable('characterTable', col);
 | 
						|
                    }
 | 
						|
                });
 | 
						|
            }
 | 
						|
 | 
						|
            function sortTable(tableId, col) {
 | 
						|
                const table = document.getElementById(tableId);
 | 
						|
                const rows = Array.from(table.tBodies[0].rows).map(row => {
 | 
						|
                    return {
 | 
						|
                        element: row,
 | 
						|
                        data: Array.from(row.cells).map(cell => {
 | 
						|
                            // Try to convert numeric strings to numbers for sorting
 | 
						|
                            const cellText = cell.textContent.trim();
 | 
						|
                            const number = parseFloat(cellText.replace(/,/g, ""));
 | 
						|
                            return isNaN(number) ? cellText : number;
 | 
						|
                        })
 | 
						|
                    };
 | 
						|
                });
 | 
						|
                const dir = sortDirection[col] = !sortDirection[col];
 | 
						|
 | 
						|
                // Create a document fragment for better performance
 | 
						|
                const fragment = document.createDocumentFragment();
 | 
						|
 | 
						|
                rows.sort((a, b) => {
 | 
						|
                    const aText = a.data[col];
 | 
						|
                    const bText = b.data[col];
 | 
						|
 | 
						|
                    // Special handling for Time Left column in jobsTable (index 6)
 | 
						|
                    if (tableId === 'jobsTable' && col === 6) {
 | 
						|
                        const aTime = parseInt(a.element.cells[col].dataset.endtime) || 0;
 | 
						|
                        const bTime = parseInt(b.element.cells[col].dataset.endtime) || 0;
 | 
						|
                        // Ensures consistent sorting for time regardless of data type
 | 
						|
                        return dir
 | 
						|
                            ? (typeof bTime === "number" && typeof aTime === "number" ? bTime - aTime : aText.toString().localeCompare(bText.toString()))
 | 
						|
                            : (typeof aTime === "number" && typeof bTime === "number" ? aTime - bTime : aText.toString().localeCompare(bText.toString()));
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Numeric sort if both are numbers
 | 
						|
                    if (!isNaN(aText) && !isNaN(bText) && aText !== "" && bText !== "") {
 | 
						|
                        return dir ? bText - aText : aText - bText;
 | 
						|
                    }
 | 
						|
                    // Return comparison by trying to respect data types
 | 
						|
                    return dir
 | 
						|
                        ? (typeof bText === "number" && typeof aText === "number" ? bText - aText : bText.toString().localeCompare(aText.toString()))
 | 
						|
                        : (typeof aText === "number" && typeof bText === "number" ? aText - bText : aText.toString().localeCompare(bText.toString()));
 | 
						|
                });
 | 
						|
 | 
						|
                rows.map(row => row.element).forEach(element => fragment.appendChild(element));
 | 
						|
                const tbody = table.tBodies[0];
 | 
						|
                tbody.innerHTML = '';
 | 
						|
                tbody.appendChild(fragment);
 | 
						|
 | 
						|
                Array.from(table.querySelectorAll("th")).forEach((th, idx) => {
 | 
						|
                    th.classList.remove("sorted-asc", "sorted-desc");
 | 
						|
                    if (idx === col) {
 | 
						|
                        th.classList.add(dir ? "sorted-desc" : "sorted-asc");
 | 
						|
                    }
 | 
						|
                });
 | 
						|
            }
 | 
						|
 | 
						|
            function formatDuration(seconds) {
 | 
						|
                if (seconds <= 0) return "Completed";
 | 
						|
                const d = Math.floor(seconds / 86400);
 | 
						|
                const h = Math.floor((seconds % 86400) / 3600);
 | 
						|
                const m = Math.floor((seconds % 3600) / 60);
 | 
						|
                const s = Math.floor(seconds % 60);
 | 
						|
                return `${d > 0 ? d + "d " : ""}${h}h ${m}m ${s}s`;
 | 
						|
            }
 | 
						|
 | 
						|
            function updateCountdowns() {
 | 
						|
                const now = Math.floor(Date.now() / 1000);
 | 
						|
                const cells = document.querySelectorAll(".time-left");
 | 
						|
 | 
						|
                cells.forEach(cell => {
 | 
						|
                    const end = parseInt(cell.dataset.endtime);
 | 
						|
                    if (isNaN(end) || end === 0) return;
 | 
						|
 | 
						|
                    const secondsLeft = end - now;
 | 
						|
                    // Update all timers every second
 | 
						|
                    cell.textContent = formatDuration(secondsLeft);
 | 
						|
 | 
						|
                    if (secondsLeft <= 3600) {
 | 
						|
                        cell.classList.add("soon");
 | 
						|
                    } else {
 | 
						|
                        cell.classList.remove("soon");
 | 
						|
                    }
 | 
						|
                });
 | 
						|
 | 
						|
                // Schedule next update using requestAnimationFrame for smooth animation
 | 
						|
                if (cells.length > 0) {
 | 
						|
                    animationFrameId = requestAnimationFrame(() => {
 | 
						|
                        setTimeout(updateCountdowns, 1000);
 | 
						|
                    });
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            // Progressive loading of character data
 | 
						|
            let remainingCharacters = <?php echo json_encode(
 | 
						|
                array_slice($character_ids, 1)
 | 
						|
            ); ?>;
 | 
						|
            let loadTimeout;
 | 
						|
            function loadNextCharacter() {
 | 
						|
                // Clear any existing timeout
 | 
						|
                if (loadTimeout) {
 | 
						|
                    clearTimeout(loadTimeout);
 | 
						|
                }
 | 
						|
 | 
						|
                // Remove any empty characters from the start
 | 
						|
                while (remainingCharacters.length > 0 && (!remainingCharacters[0] || remainingCharacters[0] === "")) {
 | 
						|
                    remainingCharacters.shift();
 | 
						|
                }
 | 
						|
 | 
						|
                if (remainingCharacters.length === 0) {
 | 
						|
                    jobsLoading = false;
 | 
						|
                    jobsTableHead.style.cursor = "pointer";
 | 
						|
                    if (lastSortedCol !== null) {
 | 
						|
                        sortTable('jobsTable', lastSortedCol);
 | 
						|
                    }
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
 | 
						|
                const charId = remainingCharacters.shift();
 | 
						|
                const loadingRow = document.getElementById(`loading-${charId}`);
 | 
						|
                if (!loadingRow) {
 | 
						|
                    setTimeout(loadNextCharacter, 100);
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
 | 
						|
                // Set a timeout for this character's loading
 | 
						|
                loadTimeout = setTimeout(() => {
 | 
						|
                    console.log(`Loading timed out for character ${charId}`);
 | 
						|
                    if (loadingRow) {
 | 
						|
                        loadingRow.remove();
 | 
						|
                    }
 | 
						|
                    setTimeout(loadNextCharacter, 100);
 | 
						|
                }, 10000); // 10 second timeout
 | 
						|
 | 
						|
                fetch(`dashboard.php?ajax=1&character_id=${charId}&t=${Date.now()}`)
 | 
						|
                    .then(response => {
 | 
						|
                        if (!response.ok) throw new Error('Network response was not ok');
 | 
						|
                        return response.json();
 | 
						|
                    })
 | 
						|
                    .then(data => {
 | 
						|
                        clearTimeout(loadTimeout);
 | 
						|
 | 
						|
                        if (data.jobs && data.jobs.length > 0) {
 | 
						|
                            const fragment = document.createDocumentFragment();
 | 
						|
 | 
						|
                            data.jobs.forEach(job => {
 | 
						|
                                if (!job || !job.character) return; // Skip invalid jobs
 | 
						|
                                const row = document.createElement('tr');
 | 
						|
                                row.innerHTML = `
 | 
						|
                                    <td>${job.character || ''}</td>
 | 
						|
                                    <td>${job.blueprint || ''}</td>
 | 
						|
                                    <td>${job.activity || ''}</td>
 | 
						|
                                    <td>${job.location || ''}</td>
 | 
						|
                                    <td>${job.start_time || ''}</td>
 | 
						|
                                    <td>${job.end_time || ''}</td>
 | 
						|
                                    <td class="time-left" data-endtime="${job.end_time_unix || ''}">${job.time_left || ''}</td>
 | 
						|
                                `;
 | 
						|
                                fragment.appendChild(row);
 | 
						|
                            });
 | 
						|
 | 
						|
                            if (loadingRow && loadingRow.parentNode) {
 | 
						|
                                loadingRow.parentNode.replaceChild(fragment, loadingRow);
 | 
						|
                            } else {
 | 
						|
                                const tbody = document.querySelector('#jobsTable tbody');
 | 
						|
                                if (tbody) tbody.appendChild(fragment);
 | 
						|
                            }
 | 
						|
                        } else if (loadingRow) {
 | 
						|
                            loadingRow.innerHTML = '<td colspan="7">No jobs found</td>';
 | 
						|
                            setTimeout(() => loadingRow.remove(), 2000);
 | 
						|
                        }
 | 
						|
 | 
						|
                        setTimeout(loadNextCharacter, 100);
 | 
						|
                    })
 | 
						|
                    .catch(error => {
 | 
						|
                        clearTimeout(loadTimeout);
 | 
						|
                        console.error('Error loading character data:', error);
 | 
						|
                        remainingCharacters.shift();
 | 
						|
                        if (loadingRow) {
 | 
						|
                            loadingRow.innerHTML = '<td colspan="7">Error loading data</td>';
 | 
						|
                            setTimeout(() => loadingRow.remove(), 2000);
 | 
						|
                        }
 | 
						|
                        setTimeout(loadNextCharacter, 100);
 | 
						|
                    });
 | 
						|
            }
 | 
						|
            // Start loading after a short delay
 | 
						|
            setTimeout(loadNextCharacter, 500);
 | 
						|
 | 
						|
            // Retry loading a specific character
 | 
						|
            window.retryLoadCharacter = function(charId) {
 | 
						|
                const retryRow = document.querySelector(`#jobsTable tbody tr td[colspan="7"]:contains("Error loading data")`).parentNode;
 | 
						|
                retryRow.innerHTML = `<td colspan="7"><div class="loader"></div> Loading jobs...</td>`;
 | 
						|
 | 
						|
                fetch(`dashboard.php?ajax=1&character_id=${charId}&t=${Date.now()}`)
 | 
						|
                    .then(response => response.json())
 | 
						|
                    .then(data => {
 | 
						|
                        if (data.jobs && data.jobs.length > 0) {
 | 
						|
                            const fragment = document.createDocumentFragment();
 | 
						|
                            data.jobs.forEach(job => {
 | 
						|
                                const row = document.createElement('tr');
 | 
						|
                                row.innerHTML = `
 | 
						|
                                    <td>${job.character}</td>
 | 
						|
                                    <td>${job.blueprint}</td>
 | 
						|
                                    <td>${job.activity}</td>
 | 
						|
                                    <td>${job.location}</td>
 | 
						|
                                    <td>${job.start_time}</td>
 | 
						|
                                    <td>${job.end_time}</td>
 | 
						|
                                    <td class="time-left" data-endtime="${job.end_time_unix}">${job.time_left}</td>
 | 
						|
                                `;
 | 
						|
                                fragment.appendChild(row);
 | 
						|
                            });
 | 
						|
                            retryRow.parentNode.replaceChild(fragment, retryRow);
 | 
						|
                        } else {
 | 
						|
 | 
						|
                        }
 | 
						|
                    })
 | 
						|
                    .catch(error => {
 | 
						|
                        console.error('Error retrying character data load:', error);
 | 
						|
                        retryRow.innerHTML = `<td colspan="7">Error loading data. <a href="#" onclick="retryLoadCharacter('${charId}'); return false;" style="color: #00aaff;">Retry</a></td>`;
 | 
						|
                    });
 | 
						|
            };
 | 
						|
 | 
						|
            // Start the update process
 | 
						|
            updateCountdowns();
 | 
						|
 | 
						|
            // Clean up animation frame when leaving the page
 | 
						|
            window.addEventListener('beforeunload', () => {
 | 
						|
                if (animationFrameId) {
 | 
						|
                    cancelAnimationFrame(animationFrameId);
 | 
						|
                }
 | 
						|
            });
 | 
						|
 | 
						|
            // Add page reload every 60 minutes for guaranteed fresh data
 | 
						|
            setInterval(() => {
 | 
						|
                if (pageVisible) {
 | 
						|
                    console.log("Scheduled page refresh for fresh data");
 | 
						|
                    window.location.reload();
 | 
						|
                }
 | 
						|
            }, 60 * 60 * 1000); // 60 minutes
 | 
						|
        });
 | 
						|
    </script>
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
    <?php // Increment visit count asynchronously to improve page load time
 | 
						|
 | 
						|
if (!function_exists("fastcgi_finish_request")) {
 | 
						|
        // If not using PHP-FPM, just do it inline
 | 
						|
        $fp = fopen("visits.txt", "c+");
 | 
						|
        if (flock($fp, LOCK_EX)) {
 | 
						|
            $count = (int) fread($fp, 100);
 | 
						|
            rewind($fp);
 | 
						|
            $count++;
 | 
						|
            fwrite($fp, $count);
 | 
						|
            fflush($fp);
 | 
						|
            flock($fp, LOCK_UN);
 | 
						|
        }
 | 
						|
        fclose($fp);
 | 
						|
    } else {
 | 
						|
        // Register shutdown function for faster page load
 | 
						|
        register_shutdown_function(function () {
 | 
						|
            $fp = fopen("visits.txt", "c+");
 | 
						|
            if (flock($fp, LOCK_EX)) {
 | 
						|
                $count = (int) fread($fp, 100);
 | 
						|
                rewind($fp);
 | 
						|
                $count++;
 | 
						|
                fwrite($fp, $count);
 | 
						|
                fflush($fp);
 | 
						|
                flock($fp, LOCK_UN);
 | 
						|
            }
 | 
						|
            fclose($fp);
 | 
						|
        });
 | 
						|
    } ?>
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
    <script>
 | 
						|
        document.addEventListener("DOMContentLoaded", () => {
 | 
						|
            function keepSessionAlive() {
 | 
						|
                // Add cache-busting parameter to prevent browser caching
 | 
						|
                fetch("keep_alive.php?t=" + Date.now())
 | 
						|
                    .then(response => response.json())
 | 
						|
                    .then(data => {
 | 
						|
                        if (data.status !== "success") {
 | 
						|
                            console.error("Failed to refresh session:", data.message);
 | 
						|
                            // If session keep-alive fails, reload the page
 | 
						|
                            window.location.reload();
 | 
						|
                        } else {
 | 
						|
                            // If tokens were refreshed or cache was cleared, reload the page to get fresh data
 | 
						|
                            if (data.tokens_refreshed || data.cache_cleared) {
 | 
						|
                                console.log("Tokens refreshed or cache cleared, reloading for fresh data");
 | 
						|
                                window.location.reload();
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    })
 | 
						|
                    .catch(error => {
 | 
						|
                        console.error("Error in keep-alive request:", error);
 | 
						|
                        // On network errors, try to reload after a delay
 | 
						|
                        setTimeout(() => window.location.reload(), 30000); // 30 seconds
 | 
						|
                    });
 | 
						|
            }
 | 
						|
 | 
						|
            // Call keepSessionAlive periodically
 | 
						|
            setInterval(keepSessionAlive, 300000); // Every 5 minutes
 | 
						|
 | 
						|
            // Also call it on user activity after inactivity
 | 
						|
            let userInactive = false;
 | 
						|
            let inactivityTimer;
 | 
						|
 | 
						|
            // Set up inactivity detection
 | 
						|
            const setInactive = () => {
 | 
						|
                userInactive = true;
 | 
						|
                inactivityTimer = setTimeout(() => {
 | 
						|
                    // User has been inactive for 10 minutes
 | 
						|
                    console.log("User inactive for 10 minutes");
 | 
						|
                }, 600000); // 10 minutes
 | 
						|
            };
 | 
						|
 | 
						|
            const setActive = () => {
 | 
						|
                // If user was inactive and is now active again
 | 
						|
                if (userInactive) {
 | 
						|
                    userInactive = false;
 | 
						|
                    clearTimeout(inactivityTimer);
 | 
						|
                    // Call keep-alive to get fresh data
 | 
						|
                    keepSessionAlive();
 | 
						|
                }
 | 
						|
            };
 | 
						|
 | 
						|
            // Set inactive after 2 minutes of no activity
 | 
						|
            setTimeout(setInactive, 120000);
 | 
						|
 | 
						|
            // Reset activity state on user interaction
 | 
						|
            ['mousemove', 'keydown', 'click', 'scroll'].forEach(event => {
 | 
						|
                document.addEventListener(event, setActive, false);
 | 
						|
            });
 | 
						|
 | 
						|
            // Initial call to keepSessionAlive
 | 
						|
            keepSessionAlive();
 | 
						|
        });
 | 
						|
    </script>
 | 
						|
</body>
 | 
						|
</html>
 |