client/addons/admin/ui/_site/script.js
Jacob Schmidt f8391463b2
All checks were successful
Build / Build (push) Successful in 28s
feat: Implement timesheets and pending payments in bank UI
This commit introduces the timesheet submission feature and displays pending payments in the bank UI.

The following changes were made:

- Added a "Submit Timesheet" action tile to the bank UI.
- Implemented the `handleTimesheet` function in `script.js` to handle timesheet submissions.
- Updated the UI to display pending payments based on player rating and a server-side multiplier.
- Modified server-side event handling to process timesheet submissions and calculate payments.
- Added a refresh timer to update player data every 30 seconds.
- Updated the player load event to include the player's rating.
2025-05-11 01:19:44 -05:00

488 lines
15 KiB
JavaScript

/**
* Admin Panel Management Script
* This script handles the admin panel functionality for the Arma 3 game interface.
* It provides player management, money operations, messaging, and other admin functions.
*/
//=============================================================================
// #region DATA STRUCTURES AND VARIABLES
//=============================================================================
/**
* Admin data structure - will be populated from the game
* Contains player information and payday amount configuration
*/
let adminData = {
players: [], // List of all players with their details
paydayAmounts: {} // Map of paygrade to bonus amount
};
/**
* Currently selected player ID for operations that require a player selection
* @type {string|null}
*/
let selectedPlayerId = null;
// #endregion
//=============================================================================
// #region INITIALIZATION AND DATA REQUESTS
//=============================================================================
/**
* Initialize the admin panel
* Sets up the UI, requests initial data from the game engine
*/
function initializeAdmin() {
updateStats();
setupFilterListeners();
requestPlayerData();
requestPaygradeData();
}
/**
* Request player data from the game engine
* Sends an event to fetch current player information
*/
function requestPlayerData() {
const message = {
event: "REQUEST::PLAYER::DATA",
data: {}
};
// Send request to the game engine
A3API.SendAlert(JSON.stringify(message));
}
/**
* Request paygrade data from the game engine
* Sends an event to fetch current paygrade configuration
*/
function requestPaygradeData() {
const message = {
event: "REQUEST::PAYGRADE::DATA",
data: {}
};
// Send request to the game engine
A3API.SendAlert(JSON.stringify(message));
}
/**
* Set up a timer to periodically refresh player data
* Ensures the UI is updated with the latest information
*/
function setupRefreshTimer() {
setInterval(requestPlayerData, 30000); // Refresh every 30 seconds
}
// #endregion
//=============================================================================
// #region DATA HANDLERS
//=============================================================================
/**
* Handle paygrade data received from the game engine
* Processes the paygrade list and updates the UI accordingly
*
* @param {Array} paygradeList - List of paygrade objects with paygrade and bonus properties
*/
function handlePaygradeDataRequest(paygradeList) {
try {
// Convert the paygrade list to a map for easier lookup
const paygradeMap = {};
paygradeList.forEach(item => {
paygradeMap[item.paygrade] = item.bonus;
});
adminData.paydayAmounts = paygradeMap;
// Update the player list if we already have player data
if (adminData.players.length > 0) {
updatePlayerList();
}
console.log("Paygrade data updated successfully");
} catch (error) {
console.error("Error updating paygrade data:", error);
}
}
/**
* Handle player data received from the game engine
* Updates the admin panel with current player information
*
* @param {Array} playerList - List of player objects with their details
*/
function handlePlayerDataRequest(playerList) {
adminData.players = playerList;
updateStats();
updatePlayerList();
}
// #endregion
//=============================================================================
// #region UI UPDATES AND DISPLAY
//=============================================================================
/**
* Update header statistics
* Shows counts of online players and staff
*/
function updateStats() {
const onlinePlayers = adminData.players.length;
const onlineStaff = adminData.players.filter(p => p.paygrade !== "E1").length;
document.getElementById('playerCount').textContent = onlinePlayers;
document.getElementById('staffCount').textContent = onlineStaff;
}
/**
* Set up filter button listeners
* Configures the filter buttons and search functionality
*/
function setupFilterListeners() {
const filterButtons = document.querySelectorAll('.filter-btn');
// Set up filter button click handlers
filterButtons.forEach(button => {
button.addEventListener('click', () => {
filterButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
filterPlayers(button.dataset.filter);
});
});
// Set up search functionality
const searchInput = document.getElementById('playerSearch');
searchInput.addEventListener('input', () => {
const activeFilter = document.querySelector('.filter-btn.active').dataset.filter;
filterPlayers(activeFilter, searchInput.value);
});
}
/**
* Filter players based on category and search term
*
* @param {string} filter - The filter category (all, staff, blufor, etc.)
* @param {string} searchTerm - Optional search term to filter by name
*/
function filterPlayers(filter, searchTerm = '') {
let filteredPlayers = adminData.players;
// Apply category filter
if (filter === 'staff') {
filteredPlayers = filteredPlayers.filter(p => p.paygrade !== "E1");
} else if (filter === 'blufor') {
filteredPlayers = filteredPlayers.filter(p => p.side === "WEST");
} else if (filter === 'opfor') {
filteredPlayers = filteredPlayers.filter(p => p.side === "EAST");
} else if (filter === 'independent') {
filteredPlayers = filteredPlayers.filter(p => p.side === "GUER");
} else if (filter === 'civilian') {
filteredPlayers = filteredPlayers.filter(p => p.side === "CIV");
}
// Apply search filter
if (searchTerm) {
const term = searchTerm.toLowerCase();
filteredPlayers = filteredPlayers.filter(p =>
p.name.toLowerCase().includes(term)
);
}
updatePlayerList(filteredPlayers);
}
/**
* Update the player list display
* Renders the filtered player list with all relevant information
*
* @param {Array} players - List of player objects to display, defaults to all players
*/
function updatePlayerList(players = adminData.players) {
const playerList = document.getElementById('playerList');
playerList.innerHTML = players.map(player => {
const paydayAmount = adminData.paydayAmounts[player.paygrade] || 1000;
const rankClass = getRankClass(player.paygrade);
return `
<div class="player-item" data-id="${player.uid}">
<div class="player-info">
<span class="player-name">${player.name}</span>
<span class="player-rank rank-${rankClass}">${player.paygrade}</span>
<span class="player-money">${parseInt(player.funds).toLocaleString()}</span>
<span class="player-payday">Payday: ${paydayAmount.toLocaleString()}</span>
<span class="player-side side-${player.side.toLowerCase()}">${player.side}</span>
</div>
<div class="player-actions">
<button class="action-btn promote-btn" onclick="updatePaygrade('${player.uid}', true)">
Promote
</button>
<button class="action-btn demote-btn" onclick="updatePaygrade('${player.uid}', false)">
Demote
</button>
<button class="action-btn message-btn" onclick="openMessageModal('${player.uid}', '${player.name}')">Message</button>
<button class="action-btn" onclick="openMoneyModal('${player.uid}')">Modify Money</button>
</div>
</div>
`}).join('');
}
/**
* Helper function to determine rank class based on paygrade
* Used for styling different ranks with appropriate CSS classes
*
* @param {string} paygrade - The player's paygrade code
* @returns {string} CSS class name for the rank
*/
function getRankClass(paygrade) {
if (paygrade.startsWith('E')) {
return 'enlisted';
} else if (paygrade.startsWith('WO')) {
return 'warrant';
} else if (paygrade.startsWith('O') ||
paygrade.startsWith('1') ||
paygrade.startsWith('2') ||
paygrade.startsWith('C') ||
paygrade.startsWith('M')) {
return 'officer';
} else {
return 'enlisted'; // Default
}
}
// #endregion
//=============================================================================
// #region RANK MANAGEMENT
//=============================================================================
/**
* Update a player's paygrade (promote or demote)
*
* @param {string} uid - Player's unique identifier
* @param {boolean} isPromotion - True for promotion, false for demotion
*/
function updatePaygrade(uid, isPromotion) {
const player = adminData.players.find(p => p.uid === uid);
if (!player) return;
// Use the paygrades from the configuration
const paygrades = Object.keys(adminData.paydayAmounts);
paygrades.sort((a, b) => adminData.paydayAmounts[a] - adminData.paydayAmounts[b]); // Sort by payment amount
const currentIndex = paygrades.indexOf(player.paygrade);
let newPaygrade;
if (isPromotion && currentIndex < paygrades.length - 1) {
newPaygrade = paygrades[currentIndex + 1];
} else if (!isPromotion && currentIndex > 0) {
newPaygrade = paygrades[currentIndex - 1];
} else {
return; // Can't promote/demote further
}
const message = {
event: "UPDATE::PAYGRADE",
data: [uid, newPaygrade]
};
A3API.SendAlert(JSON.stringify(message));
// Optimistic update
player.paygrade = newPaygrade;
updatePlayerList();
}
// #endregion
//=============================================================================
// #region MONEY MANAGEMENT
//=============================================================================
/**
* Open the money modification modal for a player
*
* @param {string} uid - Player's unique identifier
*/
function openMoneyModal(uid) {
selectedPlayerId = uid;
const modal = document.getElementById('moneyModal');
modal.style.display = 'block';
}
/**
* Close the money modification modal
*/
function closeMoneyModal() {
const modal = document.getElementById('moneyModal');
modal.style.display = 'none';
document.getElementById('moneyAmount').value = '';
selectedPlayerId = null;
}
/**
* Give money to the selected player
*/
function giveMoney() {
const amount = parseInt(document.getElementById('moneyAmount').value);
if (amount && selectedPlayerId) {
handleTransferFunds("advance", amount, selectedPlayerId);
closeMoneyModal();
}
}
/**
* Give money to all players
*/
function giveAllMoney() {
const amount = parseInt(document.getElementById('giveAllAmount').value);
const message = {
event: "ADVANCE::ALL",
data: [amount]
}
A3API.SendAlert(JSON.stringify(message));
// Request updated player data after giving money to all players
setTimeout(requestPlayerData, 500); // Short delay to allow server processing
}
/**
* Take money from the selected player
*/
function takeMoney() {
const amount = parseInt(document.getElementById('moneyAmount').value);
if (amount && selectedPlayerId) {
handleTransferFunds("deduct", amount, selectedPlayerId);
closeMoneyModal();
}
}
/**
* Handle funds transfer for a player
*
* @param {string} condition - "advance" to give money, "deduct" to take money
* @param {number} amount - Amount of money to transfer
* @param {string} uid - Player's unique identifier
*/
function handleTransferFunds(condition, amount, uid) {
const message = {
event: "HANDLE::TRANSFER",
data: [condition, amount, uid]
};
A3API.SendAlert(JSON.stringify(message));
// Optimistic update
const player = adminData.players.find(p => p.uid === uid);
if (player) {
if (condition === "advance") {
player.funds = parseInt(player.funds) + amount;
} else if (condition === "deduct") {
player.funds = Math.max(0, parseInt(player.funds) - amount);
}
updatePlayerList();
}
}
// #endregion
//=============================================================================
// #region MESSAGE SYSTEM
//=============================================================================
/**
* Open the message modal for a player
*
* @param {string} uid - Player's unique identifier
* @param {string} playerName - Player's name for display
*/
function openMessageModal(uid, playerName) {
selectedPlayerId = uid;
const modal = document.getElementById('messageModal');
document.getElementById('messagePlayerName').textContent = playerName;
modal.style.display = 'block';
}
/**
* Close the message modal
*/
function closeMessageModal() {
const modal = document.getElementById('messageModal');
modal.style.display = 'none';
document.getElementById('messageInput').value = '';
selectedPlayerId = null;
}
/**
* Send a message to the selected player
*/
function sendPlayerMessage() {
const message = document.getElementById('messageInput').value;
if (message && selectedPlayerId) {
const messageData = {
event: "SEND::MESSAGE",
data: [selectedPlayerId, message]
};
A3API.SendAlert(JSON.stringify(messageData));
closeMessageModal();
}
}
/**
* Broadcast a message to all players
*/
function broadcastMessage() {
const message = document.getElementById('broadcastMessage').value;
if (message) {
const messageData = {
event: "BROADCAST::MESSAGE",
data: ["", message]
};
A3API.SendAlert(JSON.stringify(messageData));
document.getElementById('broadcastMessage').value = '';
}
}
// #endregion
//=============================================================================
// #region GLOBAL ACTIONS
//=============================================================================
/**
* Trigger a payday for all players
*/
function Payday() {
const message = {
event: "HANDLE::PAYDAY",
data: []
};
A3API.SendAlert(JSON.stringify(message));
// Request updated player data after payday
setTimeout(requestPlayerData, 500); // Short delay to allow server processing
}
// #endregion
//=============================================================================
// #region EVENT LISTENERS
//=============================================================================
/**
* Initialize when DOM is loaded
*/
document.addEventListener('DOMContentLoaded', () => {
initializeAdmin();
setupRefreshTimer();
});
// #endregion