Torn - Training Tracker

Tracks how many times each employee has been trained. Reset all counts with one button.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Torn - Training Tracker 
// @namespace    https://www.torn.com/
// @version      1.1
// @description  Tracks how many times each employee has been trained. Reset all counts with one button.
// @author       Systoned
// @match        https://www.torn.com/companies.php*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    const STORAGE_KEY = 'resilience_train_counts';

    function getCounts() {
        try {
            return JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
        } catch (e) {
            return {};
        }
    }

    function saveCounts(counts) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(counts));
    }

    function injectCountBadge(li, id) {
        const counts = getCounts();
        const count = counts[id] || 0;

        const existing = li.querySelector('.tt-count');
        if (existing) {
            existing.textContent = `Trained: ${count}`;
            return;
        }

        const trainDiv = li.querySelector('.train');
        if (!trainDiv) return;

        const badge = document.createElement('div');
        badge.className = 'tt-count';
        badge.style.cssText = `
            clear: both;
            padding: 4px 10px;
            margin: 4px 0 0 0;
            background: #2a2a2a;
            color: #aaa;
            font-size: 12px;
            border-radius: 4px;
            display: inline-block;
        `;
        badge.textContent = `Trained: ${count}`;

        // Inject as a sibling after .train, not inside it
        trainDiv.insertAdjacentElement('afterend', badge);
    }

    function injectResetButton() {
        const existing = document.getElementById('tt-reset-btn');
        if (existing) existing.remove();

        const target = document.querySelector('.title.p10');
        if (!target) return;

        const btn = document.createElement('button');
        btn.id = 'tt-reset-btn';
        btn.textContent = 'Reset Train Counts';
        btn.style.cssText = `
            margin-left: 12px;
            padding: 4px 10px;
            background: #8b0000;
            color: #fff;
            border: none;
            border-radius: 4px;
            font-size: 12px;
            cursor: pointer;
            vertical-align: middle;
        `;

        btn.addEventListener('click', () => {
            if (confirm('Reset all training counts to zero?')) {
                saveCounts({});
                document.querySelectorAll('.tt-count').forEach(el => {
                    el.textContent = 'Trained: 0';
                });
            }
        });

        target.appendChild(btn);
    }

    function processEmployees() {
        const rows = document.querySelectorAll('li[data-user]');
        rows.forEach(li => {
            const id = li.getAttribute('data-user');
            if (!id) return;
            injectCountBadge(li, id);
        });

        injectResetButton();
    }

    // Watch for AJAX train completions
    $('body').on('ajaxComplete', function (e, xhr, settings) {
        const target = (settings.url || '') + (settings.data || '');
        if (!target.includes('trainemp2')) return;

        let data;
        try {
            data = JSON.parse(xhr.responseText);
        } catch (err) {
            return;
        }

        if (!data.success) return;

        const idMatch = target.match(/ID=(\d+)/);
        if (!idMatch) return;

        const id = idMatch[1];
        const counts = getCounts();
        counts[id] = (counts[id] || 0) + 1;
        saveCounts(counts);

        const li = document.querySelector(`li[data-user="${id}"]`);
        if (li) injectCountBadge(li, id);
    });

    function init() {
        if (!window.location.href.includes('companies.php')) return;

        const employeesPanel = document.getElementById('employees');
        if (!employeesPanel) return;

        let debounce;
        const observer = new MutationObserver(() => {
            clearTimeout(debounce);
            debounce = setTimeout(processEmployees, 300);
        });

        observer.observe(employeesPanel, { childList: true, subtree: true });

        // Run once in case the tab is already visible on load
        processEmployees();
    }

    if (document.readyState === 'complete') {
        init();
    } else {
        window.addEventListener('load', init);
    }

})();