refactor: Remove tasks.json and update documentation in store functions
All checks were successful
Build / Build (push) Successful in 28s
This commit removes the `tasks.json` file from the `.vscode` directory. Additionally, it enhances the documentation in `fnc_buyItem.sqf` and `fnc_buyVehicle.sqf` by providing clearer descriptions of item and vehicle types. The `fnc_handlePurchase.sqf` has also been updated to improve variable scoping for better code clarity.
24
.vscode/tasks.json
vendored
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "Start Arma 3 Server",
|
|
||||||
"type": "process",
|
|
||||||
"command": "wscript.exe",
|
|
||||||
"args": [
|
|
||||||
"D:\\SteamLibrary\\steamapps\\common\\Arma 3\\start_serverhub_hidden.vbs"
|
|
||||||
],
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "silent",
|
|
||||||
"panel": "shared",
|
|
||||||
"showReuseMessage": false,
|
|
||||||
"clear": true
|
|
||||||
},
|
|
||||||
"problemMatcher": [],
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
107
addons/admin/ui/_site/index.html
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Forge Admin Panel</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<script src="script.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header-content">
|
||||||
|
<h1>Admin Panel</h1>
|
||||||
|
<div class="admin-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span>👥</span>
|
||||||
|
<div>
|
||||||
|
<div>Online Players</div>
|
||||||
|
<div id="playerCount">0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span>👑</span>
|
||||||
|
<div>
|
||||||
|
<div>Staff Online</div>
|
||||||
|
<div id="staffCount">0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header> <main class="container">
|
||||||
|
<div class="sections-grid">
|
||||||
|
<div class="action-sections">
|
||||||
|
<!-- Global Actions Section -->
|
||||||
|
<div class="admin-section">
|
||||||
|
<h2>Global Actions</h2>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="paydayAmount">Payday Amount</label>
|
||||||
|
<input type="number" id="paydayAmount" min="0" value="1000">
|
||||||
|
</div>
|
||||||
|
<button class="submit-btn" onclick="triggerPayday()">Trigger Payday</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Message System Section -->
|
||||||
|
<div class="admin-section">
|
||||||
|
<h2>Broadcast Message</h2>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="broadcastMessage">Message</label>
|
||||||
|
<input type="text" id="broadcastMessage" placeholder="Enter message to broadcast...">
|
||||||
|
</div>
|
||||||
|
<button class="submit-btn" onclick="broadcastMessage()">Send to All</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Player List Section -->
|
||||||
|
<div class="admin-section player-list-section">
|
||||||
|
<div class="search-bar">
|
||||||
|
<input type="text" id="playerSearch" class="search-input" placeholder="Search players...">
|
||||||
|
<div class="filter-bar">
|
||||||
|
<button class="filter-btn active" data-filter="all">All</button>
|
||||||
|
<button class="filter-btn" data-filter="online">Online</button>
|
||||||
|
<button class="filter-btn" data-filter="staff">Staff</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="player-list" id="playerList">
|
||||||
|
<!-- Players will be populated dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Message Modal -->
|
||||||
|
<div id="messageModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Send Message</h2>
|
||||||
|
<button class="close-modal" onclick="closeMessageModal()">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="messageInput">Message to <span id="messagePlayerName"></span></label>
|
||||||
|
<input type="text" id="messageInput" placeholder="Enter your message...">
|
||||||
|
</div>
|
||||||
|
<button class="submit-btn" onclick="sendPlayerMessage()">Send Message</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Money Modal -->
|
||||||
|
<div id="moneyModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Modify Money</h2>
|
||||||
|
<button class="close-modal" onclick="closeMoneyModal()">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="moneyAmount">Amount</label>
|
||||||
|
<input type="number" id="moneyAmount" placeholder="Enter amount...">
|
||||||
|
</div>
|
||||||
|
<div class="player-actions">
|
||||||
|
<button class="action-btn promote-btn" onclick="giveMoney()">Give Money</button>
|
||||||
|
<button class="action-btn demote-btn" onclick="takeMoney()">Take Money</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
228
addons/admin/ui/_site/script.js
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
// Simulated admin data - this would be replaced with actual game data
|
||||||
|
let adminData = {
|
||||||
|
players: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "John_Doe",
|
||||||
|
role: "admin",
|
||||||
|
money: 50000,
|
||||||
|
status: "online"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Jane_Smith",
|
||||||
|
role: "mod",
|
||||||
|
money: 25000,
|
||||||
|
status: "online"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Mike_Johnson",
|
||||||
|
role: "player",
|
||||||
|
money: 10000,
|
||||||
|
status: "offline"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
paydayAmount: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
let selectedPlayerId = null;
|
||||||
|
|
||||||
|
// Initialize the admin panel
|
||||||
|
function initializeAdmin() {
|
||||||
|
updateStats();
|
||||||
|
setupFilterListeners();
|
||||||
|
updatePlayerList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update header statistics
|
||||||
|
function updateStats() {
|
||||||
|
const onlinePlayers = adminData.players.filter(p => p.status === "online").length;
|
||||||
|
const onlineStaff = adminData.players.filter(p => p.status === "online" && (p.role === "admin" || p.role === "mod")).length;
|
||||||
|
|
||||||
|
document.getElementById('playerCount').textContent = onlinePlayers;
|
||||||
|
document.getElementById('staffCount').textContent = onlineStaff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up filter button listeners
|
||||||
|
function setupFilterListeners() {
|
||||||
|
const filterButtons = document.querySelectorAll('.filter-btn');
|
||||||
|
|
||||||
|
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
|
||||||
|
function filterPlayers(filter, searchTerm = '') {
|
||||||
|
let filteredPlayers = adminData.players;
|
||||||
|
|
||||||
|
// Apply category filter
|
||||||
|
if (filter === 'online') {
|
||||||
|
filteredPlayers = filteredPlayers.filter(p => p.status === 'online');
|
||||||
|
} else if (filter === 'staff') {
|
||||||
|
filteredPlayers = filteredPlayers.filter(p => p.role === 'admin' || p.role === 'moderator');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
function updatePlayerList(players = adminData.players) {
|
||||||
|
const playerList = document.getElementById('playerList');
|
||||||
|
playerList.innerHTML = players.map(player => `
|
||||||
|
<div class="player-item" data-id="${player.id}">
|
||||||
|
<div class="player-info">
|
||||||
|
<span class="player-name">${player.name}</span>
|
||||||
|
<span class="player-role role-${player.role}">${player.role}</span>
|
||||||
|
<span class="badge badge-${player.status}">${player.status}</span>
|
||||||
|
<span class="player-money">$${player.money.toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
<div class="player-actions">
|
||||||
|
${player.role !== 'admin' ? `
|
||||||
|
<button class="action-btn promote-btn" onclick="promotePlayer(${player.id})">
|
||||||
|
${player.role === 'player' ? 'Promote to Mod' : 'Promote to Admin'}
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
${player.role !== 'player' ? `
|
||||||
|
<button class="action-btn demote-btn" onclick="demotePlayer(${player.id})">
|
||||||
|
${player.role === 'admin' ? 'Demote to Mod' : 'Demote to Player'}
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
<button class="action-btn message-btn" onclick="openMessageModal(${player.id})">Message</button>
|
||||||
|
<button class="action-btn" onclick="openMoneyModal(${player.id})">Modify Money</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Role management functions
|
||||||
|
function promotePlayer(playerId) {
|
||||||
|
const player = adminData.players.find(p => p.id === playerId);
|
||||||
|
if (player) {
|
||||||
|
if (player.role === 'player') {
|
||||||
|
player.role = 'moderator';
|
||||||
|
} else if (player.role === 'moderator') {
|
||||||
|
player.role = 'admin';
|
||||||
|
}
|
||||||
|
updatePlayerList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function demotePlayer(playerId) {
|
||||||
|
const player = adminData.players.find(p => p.id === playerId);
|
||||||
|
if (player) {
|
||||||
|
if (player.role === 'admin') {
|
||||||
|
player.role = 'moderator';
|
||||||
|
} else if (player.role === 'moderator') {
|
||||||
|
player.role = 'player';
|
||||||
|
}
|
||||||
|
updatePlayerList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Money management functions
|
||||||
|
function openMoneyModal(playerId) {
|
||||||
|
selectedPlayerId = playerId;
|
||||||
|
const modal = document.getElementById('moneyModal');
|
||||||
|
modal.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMoneyModal() {
|
||||||
|
const modal = document.getElementById('moneyModal');
|
||||||
|
modal.style.display = 'none';
|
||||||
|
document.getElementById('moneyAmount').value = '';
|
||||||
|
selectedPlayerId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function giveMoney() {
|
||||||
|
const amount = parseInt(document.getElementById('moneyAmount').value);
|
||||||
|
if (amount && selectedPlayerId) {
|
||||||
|
const player = adminData.players.find(p => p.id === selectedPlayerId);
|
||||||
|
if (player) {
|
||||||
|
player.money += amount;
|
||||||
|
updatePlayerList();
|
||||||
|
closeMoneyModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function takeMoney() {
|
||||||
|
const amount = parseInt(document.getElementById('moneyAmount').value);
|
||||||
|
if (amount && selectedPlayerId) {
|
||||||
|
const player = adminData.players.find(p => p.id === selectedPlayerId);
|
||||||
|
if (player) {
|
||||||
|
player.money = Math.max(0, player.money - amount);
|
||||||
|
updatePlayerList();
|
||||||
|
closeMoneyModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message system functions
|
||||||
|
function openMessageModal(playerId) {
|
||||||
|
selectedPlayerId = playerId;
|
||||||
|
const player = adminData.players.find(p => p.id === playerId);
|
||||||
|
const modal = document.getElementById('messageModal');
|
||||||
|
document.getElementById('messagePlayerName').textContent = player.name;
|
||||||
|
modal.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMessageModal() {
|
||||||
|
const modal = document.getElementById('messageModal');
|
||||||
|
modal.style.display = 'none';
|
||||||
|
document.getElementById('messageInput').value = '';
|
||||||
|
selectedPlayerId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendPlayerMessage() {
|
||||||
|
const message = document.getElementById('messageInput').value;
|
||||||
|
if (message && selectedPlayerId) {
|
||||||
|
const player = adminData.players.find(p => p.id === selectedPlayerId);
|
||||||
|
if (player) {
|
||||||
|
console.log(`Message sent to ${player.name}: ${message}`);
|
||||||
|
closeMessageModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function broadcastMessage() {
|
||||||
|
const message = document.getElementById('broadcastMessage').value;
|
||||||
|
if (message) {
|
||||||
|
console.log(`Broadcasting message to all players: ${message}`);
|
||||||
|
document.getElementById('broadcastMessage').value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global actions
|
||||||
|
function triggerPayday() {
|
||||||
|
const amount = parseInt(document.getElementById('paydayAmount').value);
|
||||||
|
if (amount) {
|
||||||
|
adminData.players.forEach(player => {
|
||||||
|
player.money += amount;
|
||||||
|
});
|
||||||
|
updatePlayerList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize when DOM is loaded
|
||||||
|
document.addEventListener('DOMContentLoaded', initializeAdmin);
|
313
addons/admin/ui/_site/styles.css
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sections-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-sections {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-section {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-list-section {
|
||||||
|
grid-column: span 2;
|
||||||
|
grid-row: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-list {
|
||||||
|
list-style: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 0;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-item:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-name {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-role {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-admin {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-mod {
|
||||||
|
background-color: #ffc107;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-player {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-money {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:disabled {
|
||||||
|
background-color: #6c757d;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promote-btn {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promote-btn:hover:not(:disabled) {
|
||||||
|
background-color: #218838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demote-btn {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demote-btn:hover:not(:disabled) {
|
||||||
|
background-color: #c82333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-btn {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-btn:hover:not(:disabled) {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background-color: white;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-modal {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-modal:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-online {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-offline {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-btn {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #444;
|
||||||
|
color: white;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-btn:hover {
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-btn.active {
|
||||||
|
background-color: #007bff;
|
||||||
|
}
|
99
addons/bank/ui/_site/index.html
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>FORGE - FDIC</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<script src="script.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header-content">
|
||||||
|
<h1>Federal Deposit Insurance Corporation</h1>
|
||||||
|
<div class="balance-display">
|
||||||
|
<div class="balance-item">
|
||||||
|
<span>💰</span>
|
||||||
|
<div>
|
||||||
|
<div>Wallet</div>
|
||||||
|
<div id="walletBalance">$0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="balance-item">
|
||||||
|
<span>🏦</span>
|
||||||
|
<div>
|
||||||
|
<div>Account</div>
|
||||||
|
<div id="accountBalance">$0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<div class="actions-grid"> <!-- Transfer Between Accounts -->
|
||||||
|
<div class="action-tile">
|
||||||
|
<h2>Transfer Money</h2>
|
||||||
|
<form id="transferForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="transferType">Transfer From</label>
|
||||||
|
<select id="transferType" required>
|
||||||
|
<option value="to_wallet">Account to Wallet</option>
|
||||||
|
<option value="to_account">Wallet to Account</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="transferAmount">Amount</label>
|
||||||
|
<input type="number" id="transferAmount" min="1" step="1" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="submit-btn">Transfer</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Transfer to Player -->
|
||||||
|
<div class="action-tile">
|
||||||
|
<h2>Transfer to Player</h2>
|
||||||
|
<form id="transferPlayerForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="playerSelect">Player</label>
|
||||||
|
<select id="playerSelect" required>
|
||||||
|
<option value="" disabled selected>Select Player</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="playerTransferAmount">Amount</label>
|
||||||
|
<input type="number" id="playerTransferAmount" min="1" step="1" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="submit-btn">Send Money</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Submit Timesheet -->
|
||||||
|
<div class="action-tile">
|
||||||
|
<h2>Submit Timesheet</h2>
|
||||||
|
<form id="timesheetForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="hoursWorked">Hours Worked</label>
|
||||||
|
<input type="number" id="hoursWorked" min="1" step="0.5" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="hourlyRate">Hourly Rate</label>
|
||||||
|
<input type="number" id="hourlyRate" min="1" step="1" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="submit-btn">Submit Timesheet</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Transaction History -->
|
||||||
|
<section class="history-section">
|
||||||
|
<h2>Transaction History</h2>
|
||||||
|
<ul class="history-list" id="transactionHistory">
|
||||||
|
<!-- Transactions will be added here dynamically -->
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
addons/bank/ui/_site/public/fdic.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
addons/bank/ui/_site/public/fdic_co.paa
Normal file
BIN
addons/bank/ui/_site/public/fms.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
addons/bank/ui/_site/public/fms_co.paa
Normal file
BIN
addons/bank/ui/_site/public/gms.png
Normal file
After Width: | Height: | Size: 109 KiB |
BIN
addons/bank/ui/_site/public/gms_co.paa
Normal file
147
addons/bank/ui/_site/script.js
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
// Simulated data - this would be replaced with actual game data
|
||||||
|
let bankState = {
|
||||||
|
wallet: 1000,
|
||||||
|
account: 5000,
|
||||||
|
players: [
|
||||||
|
{ id: 1, name: "Player 1" },
|
||||||
|
{ id: 2, name: "Player 2" },
|
||||||
|
{ id: 3, name: "Player 3" }
|
||||||
|
],
|
||||||
|
transactions: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize the interface
|
||||||
|
function initializeBank() {
|
||||||
|
updateBalanceDisplays();
|
||||||
|
populatePlayerList();
|
||||||
|
setupEventListeners();
|
||||||
|
loadTransactionHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update balance displays in the header
|
||||||
|
function updateBalanceDisplays() {
|
||||||
|
document.getElementById('walletBalance').textContent = `$${bankState.wallet.toLocaleString()}`;
|
||||||
|
document.getElementById('accountBalance').textContent = `$${bankState.account.toLocaleString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the player selection dropdown
|
||||||
|
function populatePlayerList() {
|
||||||
|
const playerSelect = document.getElementById('playerSelect');
|
||||||
|
playerSelect.innerHTML = '<option value="" disabled selected>Select Player</option>';
|
||||||
|
|
||||||
|
bankState.players.forEach(player => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = player.id;
|
||||||
|
option.textContent = player.name;
|
||||||
|
playerSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new transaction to history
|
||||||
|
function addTransaction(type, amount, details = '') {
|
||||||
|
const transaction = {
|
||||||
|
type,
|
||||||
|
amount,
|
||||||
|
details,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
bankState.transactions.unshift(transaction);
|
||||||
|
updateTransactionHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the transaction history display
|
||||||
|
function updateTransactionHistory() {
|
||||||
|
const historyList = document.getElementById('transactionHistory');
|
||||||
|
historyList.innerHTML = '';
|
||||||
|
|
||||||
|
bankState.transactions.forEach(transaction => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'history-item';
|
||||||
|
|
||||||
|
const isNegative = ['transfer_out', 'to_wallet'].includes(transaction.type);
|
||||||
|
const amountClass = isNegative ? 'amount-negative' : 'amount-positive';
|
||||||
|
const amountPrefix = isNegative ? '-' : '+';
|
||||||
|
|
||||||
|
li.innerHTML = `
|
||||||
|
<span class="transaction-type">${formatTransactionType(transaction.type)}</span>
|
||||||
|
<span class="transaction-details">${transaction.details}</span>
|
||||||
|
<span class="amount ${amountClass}">${amountPrefix}$${Math.abs(transaction.amount).toLocaleString()}</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
historyList.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format transaction type for display
|
||||||
|
function formatTransactionType(type) {
|
||||||
|
const types = {
|
||||||
|
'to_wallet': 'To Wallet',
|
||||||
|
'to_account': 'To Account',
|
||||||
|
'transfer_out': 'Transfer Out',
|
||||||
|
'transfer_in': 'Transfer In',
|
||||||
|
'timesheet': 'Timesheet'
|
||||||
|
};
|
||||||
|
return types[type] || type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up all form event listeners
|
||||||
|
function setupEventListeners() { // Handle transfers between wallet and account
|
||||||
|
document.getElementById('transferForm').addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const amount = parseInt(document.getElementById('transferAmount').value);
|
||||||
|
const transferType = document.getElementById('transferType').value;
|
||||||
|
|
||||||
|
if (transferType === 'to_wallet') {
|
||||||
|
if (amount > bankState.account) {
|
||||||
|
alert('Insufficient funds in account');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bankState.account -= amount;
|
||||||
|
bankState.wallet += amount;
|
||||||
|
} else {
|
||||||
|
if (amount > bankState.wallet) {
|
||||||
|
alert('Insufficient funds in wallet');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bankState.wallet -= amount;
|
||||||
|
bankState.account += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTransaction(transferType, amount);
|
||||||
|
updateBalanceDisplays();
|
||||||
|
e.target.reset();
|
||||||
|
}); // Transfer to Player
|
||||||
|
document.getElementById('transferPlayerForm').addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const amount = parseInt(document.getElementById('playerTransferAmount').value);
|
||||||
|
const playerId = document.getElementById('playerSelect').value;
|
||||||
|
const playerName = bankState.players.find(p => p.id.toString() === playerId)?.name;
|
||||||
|
|
||||||
|
if (amount > bankState.account) {
|
||||||
|
alert('Insufficient funds in account');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bankState.account -= amount;
|
||||||
|
addTransaction('transfer_out', amount, `To ${playerName}`);
|
||||||
|
updateBalanceDisplays();
|
||||||
|
e.target.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Submit Timesheet
|
||||||
|
document.getElementById('timesheetForm').addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const hours = parseFloat(document.getElementById('hoursWorked').value);
|
||||||
|
const rate = parseInt(document.getElementById('hourlyRate').value);
|
||||||
|
const amount = Math.floor(hours * rate);
|
||||||
|
|
||||||
|
bankState.account += amount;
|
||||||
|
addTransaction('timesheet', amount, `${hours} hours @ $${rate}/hr`);
|
||||||
|
updateBalanceDisplays();
|
||||||
|
e.target.reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize when DOM is loaded
|
||||||
|
document.addEventListener('DOMContentLoaded', initializeBank);
|
163
addons/bank/ui/_site/styles.css
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-display {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-item i {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-tile {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
padding: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-tile:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-tile h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input, .form-group select {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-list {
|
||||||
|
list-style: none;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-type {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-positive {
|
||||||
|
color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-negative {
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: #dc3545;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message {
|
||||||
|
color: #28a745;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
45
addons/garage/ui/_site/index.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>FORGE - Garage</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<script src="script.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header-content">
|
||||||
|
<h1>Garage</h1>
|
||||||
|
<div class="garage-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<i>🚗</i>
|
||||||
|
<div>
|
||||||
|
<div>Vehicles</div>
|
||||||
|
<div id="vehicleCount">0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<i>🔧</i>
|
||||||
|
<div>
|
||||||
|
<div>In Maintenance</div>
|
||||||
|
<div id="maintenanceCount">0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<div class="category-filters" id="categoryFilters">
|
||||||
|
<!-- Categories will be populated dynamically -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="vehicles-grid" id="vehiclesGrid">
|
||||||
|
<!-- Vehicles will be populated dynamically -->
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
184
addons/garage/ui/_site/script.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
// Simulated garage data - this would be replaced with actual game data
|
||||||
|
let garageData = {
|
||||||
|
vehicles: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Offroad Vehicle",
|
||||||
|
category: "Land",
|
||||||
|
type: "Transport",
|
||||||
|
status: "available",
|
||||||
|
fuel: 100,
|
||||||
|
damage: 0,
|
||||||
|
maintenance: 95,
|
||||||
|
lastUsed: "2025-04-10T15:30:00",
|
||||||
|
image: "placeholder.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Transport Helicopter",
|
||||||
|
category: "Air",
|
||||||
|
type: "Transport",
|
||||||
|
status: "in-use",
|
||||||
|
fuel: 75,
|
||||||
|
damage: 15,
|
||||||
|
maintenance: 80,
|
||||||
|
lastUsed: "2025-04-12T09:15:00",
|
||||||
|
image: "placeholder.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Patrol Boat",
|
||||||
|
category: "Sea",
|
||||||
|
type: "Patrol",
|
||||||
|
status: "maintenance",
|
||||||
|
fuel: 50,
|
||||||
|
damage: 35,
|
||||||
|
maintenance: 45,
|
||||||
|
lastUsed: "2025-04-11T18:45:00",
|
||||||
|
image: "placeholder.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Armed SUV",
|
||||||
|
category: "Land",
|
||||||
|
type: "Combat",
|
||||||
|
status: "available",
|
||||||
|
fuel: 90,
|
||||||
|
damage: 5,
|
||||||
|
maintenance: 88,
|
||||||
|
lastUsed: "2025-04-12T11:20:00",
|
||||||
|
image: "placeholder.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize the garage interface
|
||||||
|
function initializeGarage() {
|
||||||
|
updateStats();
|
||||||
|
setupCategoryFilters();
|
||||||
|
displayVehicles();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update garage statistics
|
||||||
|
function updateStats() {
|
||||||
|
const totalVehicles = garageData.vehicles.length;
|
||||||
|
const inMaintenance = garageData.vehicles.filter(v => v.status === 'maintenance').length;
|
||||||
|
|
||||||
|
document.getElementById('vehicleCount').textContent = totalVehicles;
|
||||||
|
document.getElementById('maintenanceCount').textContent = inMaintenance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up category filters
|
||||||
|
function setupCategoryFilters() {
|
||||||
|
const categories = ['All', ...new Set(garageData.vehicles.map(v => v.category))];
|
||||||
|
const filtersContainer = document.getElementById('categoryFilters');
|
||||||
|
|
||||||
|
categories.forEach(category => {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className = 'filter-btn' + (category === 'All' ? ' active' : '');
|
||||||
|
button.textContent = category;
|
||||||
|
button.addEventListener('click', () => filterVehicles(category));
|
||||||
|
filtersContainer.appendChild(button);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter vehicles by category
|
||||||
|
function filterVehicles(category) {
|
||||||
|
// Update active filter button
|
||||||
|
document.querySelectorAll('.filter-btn').forEach(btn => {
|
||||||
|
btn.classList.toggle('active', btn.textContent === category);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter and display vehicles
|
||||||
|
const filteredVehicles = category === 'All'
|
||||||
|
? garageData.vehicles
|
||||||
|
: garageData.vehicles.filter(v => v.category === category);
|
||||||
|
|
||||||
|
displayVehicles(filteredVehicles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display vehicles in the grid
|
||||||
|
function displayVehicles(vehicles = garageData.vehicles) {
|
||||||
|
const grid = document.getElementById('vehiclesGrid');
|
||||||
|
grid.innerHTML = '';
|
||||||
|
|
||||||
|
vehicles.forEach(vehicle => {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'vehicle-card';
|
||||||
|
|
||||||
|
const statusText = {
|
||||||
|
'available': 'Available',
|
||||||
|
'in-use': 'In Use',
|
||||||
|
'maintenance': 'Maintenance'
|
||||||
|
}[vehicle.status];
|
||||||
|
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="vehicle-image" style="background-image: url(${vehicle.image})"></div>
|
||||||
|
<div class="vehicle-info">
|
||||||
|
<div class="vehicle-header">
|
||||||
|
<div class="vehicle-name">${vehicle.name}</div>
|
||||||
|
<span class="vehicle-status status-${vehicle.status}">${statusText}</span>
|
||||||
|
</div>
|
||||||
|
<div class="vehicle-details">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">Type</span>
|
||||||
|
<span class="detail-value">${vehicle.type}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">Category</span>
|
||||||
|
<span class="detail-value">${vehicle.category}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">Fuel</span>
|
||||||
|
<div class="vehicle-stats">
|
||||||
|
<span class="stat-dot ${getFuelStatusColor(vehicle.fuel)}"></span>
|
||||||
|
<span>${vehicle.fuel}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">Condition</span>
|
||||||
|
<div class="vehicle-stats">
|
||||||
|
<span class="stat-dot ${getConditionStatusColor(vehicle.maintenance)}"></span>
|
||||||
|
<span>${vehicle.maintenance}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="vehicle-actions">
|
||||||
|
<button class="action-btn spawn-btn"
|
||||||
|
onclick="spawnVehicle(${vehicle.id})"
|
||||||
|
${vehicle.status !== 'available' ? 'disabled' : ''}>
|
||||||
|
Spawn Vehicle
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
grid.appendChild(card);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get color class for fuel status
|
||||||
|
function getFuelStatusColor(fuelLevel) {
|
||||||
|
if (fuelLevel > 66) return 'stat-green';
|
||||||
|
if (fuelLevel > 33) return 'stat-yellow';
|
||||||
|
return 'stat-red';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get color class for vehicle condition
|
||||||
|
function getConditionStatusColor(condition) {
|
||||||
|
if (condition > 66) return 'stat-green';
|
||||||
|
if (condition > 33) return 'stat-yellow';
|
||||||
|
return 'stat-red';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle vehicle spawn
|
||||||
|
function spawnVehicle(vehicleId) {
|
||||||
|
const vehicle = garageData.vehicles.find(v => v.id === vehicleId);
|
||||||
|
if (vehicle && vehicle.status === 'available') {
|
||||||
|
alert(`Spawning vehicle: ${vehicle.name}`);
|
||||||
|
// Here you would typically integrate with your game's vehicle spawning system
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize when DOM is loaded
|
||||||
|
document.addEventListener('DOMContentLoaded', initializeGarage);
|
233
addons/garage/ui/_site/styles.css
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.garage-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicles-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-card {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
background-color: #eee;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-info {
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-name {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-status {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-available {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-in-use {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-maintenance {
|
||||||
|
background-color: #ffc107;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-details {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spawn-btn {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spawn-btn:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spawn-btn:disabled {
|
||||||
|
background-color: #6c757d;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spawn-btn:disabled:hover {
|
||||||
|
background-color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintain-btn {
|
||||||
|
background-color: #ffc107;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maintain-btn:hover {
|
||||||
|
background-color: #d39e00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-filters {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-btn {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #444;
|
||||||
|
color: white;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-btn:hover {
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-btn.active {
|
||||||
|
background-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-green {
|
||||||
|
background-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-yellow {
|
||||||
|
background-color: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-red {
|
||||||
|
background-color: #dc3545;
|
||||||
|
}
|
74
addons/locker/ui/_site/index.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>FORGE - Locker</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<script src="script.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header-content">
|
||||||
|
<h1>Locker</h1>
|
||||||
|
<div class="storage-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span>📦</span>
|
||||||
|
<div>
|
||||||
|
<div>Storage Space</div>
|
||||||
|
<div id="storageSpace">0/100</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span>🎒</span>
|
||||||
|
<div>
|
||||||
|
<div>Items Stored</div>
|
||||||
|
<div id="itemCount">0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<div class="category-filters" id="categoryFilters">
|
||||||
|
<button class="filter-btn active" data-category="all">All Items</button>
|
||||||
|
<button class="filter-btn" data-category="weapons">Weapons</button>
|
||||||
|
<button class="filter-btn" data-category="clothing">Clothing</button>
|
||||||
|
<button class="filter-btn" data-category="equipment">Equipment</button>
|
||||||
|
<button class="filter-btn" data-category="magazines">Magazines</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sections-grid">
|
||||||
|
<!-- Current Equipment Section -->
|
||||||
|
<div class="equipment-section player-equipment">
|
||||||
|
<h2>Current Equipment</h2>
|
||||||
|
<div class="equipment-list" id="playerEquipment">
|
||||||
|
<!-- Player equipment will be populated dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Storage Section -->
|
||||||
|
<div class="equipment-section stored-equipment">
|
||||||
|
<h2>Stored Equipment</h2>
|
||||||
|
<div class="equipment-list" id="storedEquipment">
|
||||||
|
<!-- Stored equipment will be populated dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Equipment Details Section -->
|
||||||
|
<div class="equipment-section">
|
||||||
|
<h2>Equipment Details</h2>
|
||||||
|
<div id="equipmentDetails">
|
||||||
|
<!-- Equipment details will be populated when an item is selected -->
|
||||||
|
<div class="detail-placeholder">
|
||||||
|
Select an item to view its details
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
236
addons/locker/ui/_site/script.js
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
// Simulated locker data - this would be replaced with actual game data
|
||||||
|
let lockerData = {
|
||||||
|
storageSpace: {
|
||||||
|
used: 45,
|
||||||
|
total: 100
|
||||||
|
},
|
||||||
|
playerEquipment: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Combat Uniform",
|
||||||
|
type: "clothing",
|
||||||
|
category: "Uniform",
|
||||||
|
condition: 95
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Tactical Vest",
|
||||||
|
type: "clothing",
|
||||||
|
category: "Vest",
|
||||||
|
condition: 88
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Combat Backpack",
|
||||||
|
type: "clothing",
|
||||||
|
category: "Backpack",
|
||||||
|
condition: 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Assault Rifle",
|
||||||
|
type: "weapons",
|
||||||
|
category: "Primary",
|
||||||
|
condition: 92,
|
||||||
|
attachments: ["Scope", "Grip"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
storedItems: [
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "Pistol",
|
||||||
|
type: "weapons",
|
||||||
|
category: "Secondary",
|
||||||
|
condition: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "Medical Kit",
|
||||||
|
type: "equipment",
|
||||||
|
category: "Medical",
|
||||||
|
quantity: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "Rifle Magazines",
|
||||||
|
type: "magazines",
|
||||||
|
category: "Magazine",
|
||||||
|
quantity: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: "Combat Helmet",
|
||||||
|
type: "clothing",
|
||||||
|
category: "Headgear",
|
||||||
|
condition: 85
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize the locker interface
|
||||||
|
function initializeLocker() {
|
||||||
|
updateStats();
|
||||||
|
setupFilterListeners();
|
||||||
|
updateEquipmentLists();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update storage statistics
|
||||||
|
function updateStats() {
|
||||||
|
document.getElementById('storageSpace').textContent =
|
||||||
|
`${lockerData.storageSpace.used}/${lockerData.storageSpace.total}`;
|
||||||
|
document.getElementById('itemCount').textContent =
|
||||||
|
lockerData.storedItems.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up category filter listeners
|
||||||
|
function setupFilterListeners() {
|
||||||
|
const filterButtons = document.querySelectorAll('.filter-btn');
|
||||||
|
|
||||||
|
filterButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', (e) => {
|
||||||
|
// Update active state
|
||||||
|
filterButtons.forEach(btn => btn.classList.remove('active'));
|
||||||
|
button.classList.add('active');
|
||||||
|
|
||||||
|
// Filter items
|
||||||
|
const category = button.dataset.category;
|
||||||
|
filterEquipment(category);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter equipment by category
|
||||||
|
function filterEquipment(category) {
|
||||||
|
const playerItems = category === 'all'
|
||||||
|
? lockerData.playerEquipment
|
||||||
|
: lockerData.playerEquipment.filter(item => item.type === category);
|
||||||
|
|
||||||
|
const storedItems = category === 'all'
|
||||||
|
? lockerData.storedItems
|
||||||
|
: lockerData.storedItems.filter(item => item.type === category);
|
||||||
|
|
||||||
|
updateEquipmentLists(playerItems, storedItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update equipment lists display
|
||||||
|
function updateEquipmentLists(playerItems = lockerData.playerEquipment, storedItems = lockerData.storedItems) {
|
||||||
|
const playerList = document.getElementById('playerEquipment');
|
||||||
|
const storedList = document.getElementById('storedEquipment');
|
||||||
|
|
||||||
|
// Update player equipment
|
||||||
|
playerList.innerHTML = playerItems.map(item => `
|
||||||
|
<div class="equipment-item" data-id="${item.id}">
|
||||||
|
<div class="item-info">
|
||||||
|
<span class="item-name">${item.name}</span>
|
||||||
|
<span class="item-type">${item.category}</span>
|
||||||
|
${item.condition ? `
|
||||||
|
<span class="item-quantity">
|
||||||
|
${item.condition}%
|
||||||
|
</span>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
<button class="action-btn store-btn" onclick="storeItem(${item.id})">
|
||||||
|
Store
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
// Update stored equipment
|
||||||
|
storedList.innerHTML = storedItems.map(item => `
|
||||||
|
<div class="equipment-item" data-id="${item.id}">
|
||||||
|
<div class="item-info">
|
||||||
|
<span class="item-name">${item.name}</span>
|
||||||
|
<span class="item-type">${item.category}</span>
|
||||||
|
${item.quantity ? `
|
||||||
|
<span class="item-quantity">
|
||||||
|
x${item.quantity}
|
||||||
|
</span>
|
||||||
|
` : item.condition ? `
|
||||||
|
<span class="item-quantity">
|
||||||
|
${item.condition}%
|
||||||
|
</span>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
<button class="action-btn equip-btn" onclick="equipItem(${item.id})">
|
||||||
|
Equip
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
// Add click listeners for showing details
|
||||||
|
document.querySelectorAll('.equipment-item').forEach(item => {
|
||||||
|
item.addEventListener('click', () => showItemDetails(item.dataset.id));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show item details
|
||||||
|
function showItemDetails(itemId) {
|
||||||
|
const item = lockerData.playerEquipment.find(i => i.id === parseInt(itemId)) ||
|
||||||
|
lockerData.storedItems.find(i => i.id === parseInt(itemId));
|
||||||
|
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
const detailsDiv = document.getElementById('equipmentDetails');
|
||||||
|
detailsDiv.innerHTML = `
|
||||||
|
<h3>${item.name}</h3>
|
||||||
|
<div class="detail-grid">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">Category:</span>
|
||||||
|
<span class="detail-value">${item.category}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">Type:</span>
|
||||||
|
<span class="detail-value">${item.type}</span>
|
||||||
|
</div>
|
||||||
|
${item.condition ? `
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">Condition:</span>
|
||||||
|
<span class="detail-value">${item.condition}%</span>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
${item.quantity ? `
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">Quantity:</span>
|
||||||
|
<span class="detail-value">x${item.quantity}</span>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
${item.attachments ? `
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">Attachments:</span>
|
||||||
|
<span class="detail-value">${item.attachments.join(', ')}</span>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store an item in the locker
|
||||||
|
function storeItem(itemId) {
|
||||||
|
const itemIndex = lockerData.playerEquipment.findIndex(item => item.id === itemId);
|
||||||
|
if (itemIndex === -1) return;
|
||||||
|
|
||||||
|
const item = lockerData.playerEquipment[itemIndex];
|
||||||
|
lockerData.playerEquipment.splice(itemIndex, 1);
|
||||||
|
lockerData.storedItems.push(item);
|
||||||
|
lockerData.storageSpace.used += 1;
|
||||||
|
|
||||||
|
updateStats();
|
||||||
|
updateEquipmentLists();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equip an item from the locker
|
||||||
|
function equipItem(itemId) {
|
||||||
|
const itemIndex = lockerData.storedItems.findIndex(item => item.id === itemId);
|
||||||
|
if (itemIndex === -1) return;
|
||||||
|
|
||||||
|
const item = lockerData.storedItems[itemIndex];
|
||||||
|
lockerData.storedItems.splice(itemIndex, 1);
|
||||||
|
lockerData.playerEquipment.push(item);
|
||||||
|
lockerData.storageSpace.used -= 1;
|
||||||
|
|
||||||
|
updateStats();
|
||||||
|
updateEquipmentLists();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize when DOM is loaded
|
||||||
|
document.addEventListener('DOMContentLoaded', initializeLocker);
|
194
addons/locker/ui/_site/styles.css
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.storage-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sections-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-template-rows: auto auto;
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 1rem;
|
||||||
|
min-height: calc(100vh - 200px); /* Account for header and padding */
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-section {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-section h2 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-list {
|
||||||
|
list-style: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 0; /* This forces the flex-grow to work properly */
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-item:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-type {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-quantity {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #28a745;
|
||||||
|
background: rgba(40, 167, 69, 0.1);
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-btn {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-btn:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equip-btn {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equip-btn:hover {
|
||||||
|
background-color: #218838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equip-btn:disabled {
|
||||||
|
background-color: #6c757d;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-filters {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-btn {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #444;
|
||||||
|
color: white;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-btn:hover {
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-btn.active {
|
||||||
|
background-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-section.player-equipment {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-section.stored-equipment {
|
||||||
|
grid-column: span 1;
|
||||||
|
grid-row: span 2;
|
||||||
|
}
|
0
addons/org/ui/BaseControl.hpp
Normal file
90
addons/org/ui/_site/index.html
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>FORGE - ORG</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<script src="script.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header-content">
|
||||||
|
<h1 id="orgName">Organization Name</h1>
|
||||||
|
<div class="org-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span>⭐</span>
|
||||||
|
<div>
|
||||||
|
<div>Reputation</div>
|
||||||
|
<div id="orgReputation">0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span>💰</span>
|
||||||
|
<div>
|
||||||
|
<div>Funds</div>
|
||||||
|
<div id="orgFunds">$0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<div class="sections-grid">
|
||||||
|
<!-- Members Section -->
|
||||||
|
<div class="section-tile">
|
||||||
|
<h2>Members</h2>
|
||||||
|
<ul class="member-list" id="membersList">
|
||||||
|
<!-- Members will be populated dynamically -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vehicles Section -->
|
||||||
|
<div class="section-tile">
|
||||||
|
<h2>Vehicles</h2>
|
||||||
|
<ul class="asset-list" id="vehiclesList">
|
||||||
|
<!-- Vehicles will be populated dynamically -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Equipment Section -->
|
||||||
|
<div class="section-tile">
|
||||||
|
<h2>Equipment</h2>
|
||||||
|
<ul class="asset-list" id="equipmentList">
|
||||||
|
<!-- Equipment will be populated dynamically -->
|
||||||
|
</ul>
|
||||||
|
</div> <!-- Properties Section -->
|
||||||
|
<div class="section-tile">
|
||||||
|
<h2>Properties</h2>
|
||||||
|
<ul class="asset-list" id="propertiesList">
|
||||||
|
<!-- Properties will be populated dynamically -->
|
||||||
|
</ul>
|
||||||
|
</div> <!-- Supplies Section -->
|
||||||
|
<div class="section-tile">
|
||||||
|
<h2>Supplies</h2>
|
||||||
|
<ul class="asset-list" id="suppliesList">
|
||||||
|
<!-- Supplies will be populated dynamically -->
|
||||||
|
</ul>
|
||||||
|
</div> <!-- Transactions Section -->
|
||||||
|
<div class="section-tile">
|
||||||
|
<h2>Transactions</h2>
|
||||||
|
<ul class="transaction-list" id="transactionsList">
|
||||||
|
<!-- Transactions will be populated dynamically -->
|
||||||
|
</ul>
|
||||||
|
</div> <!-- Memos Section -->
|
||||||
|
<div class="section-tile memo-section">
|
||||||
|
<h2>Memos & Logs</h2>
|
||||||
|
<div class="memo-controls">
|
||||||
|
<button id="addMemoBtn" class="add-btn">Add Memo</button>
|
||||||
|
</div>
|
||||||
|
<ul class="memo-list" id="memosList">
|
||||||
|
<!-- Memos will be populated dynamically -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
addons/org/ui/_site/public/fdic.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
addons/org/ui/_site/public/fdic_co.paa
Normal file
BIN
addons/org/ui/_site/public/fms.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
addons/org/ui/_site/public/fms_co.paa
Normal file
BIN
addons/org/ui/_site/public/gms.png
Normal file
After Width: | Height: | Size: 109 KiB |
BIN
addons/org/ui/_site/public/gms_co.paa
Normal file
302
addons/org/ui/_site/script.js
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
// Simulated organization data - this would be replaced with actual game data
|
||||||
|
let orgData = {
|
||||||
|
name: "Black Rifle Company",
|
||||||
|
reputation: 1250,
|
||||||
|
funds: 75000,
|
||||||
|
transactions: [
|
||||||
|
{ id: 1, type: "deposit", amount: 5000, description: "Weekly income", date: "2025-04-12T10:30:00" },
|
||||||
|
{ id: 2, type: "withdrawal", amount: -2000, description: "Vehicle maintenance", date: "2025-04-11T15:45:00" },
|
||||||
|
{ id: 3, type: "deposit", amount: 3000, description: "Property rent", date: "2025-04-10T09:15:00" },
|
||||||
|
{ id: 4, type: "withdrawal", amount: -1500, description: "Equipment purchase", date: "2025-04-09T14:20:00" }
|
||||||
|
],
|
||||||
|
members: [
|
||||||
|
{ id: 1, name: "John Doe", role: "owner", status: "online" },
|
||||||
|
{ id: 2, name: "Jane Smith", role: "admin", status: "online" },
|
||||||
|
{ id: 3, name: "Mike Johnson", role: "member", status: "offline" },
|
||||||
|
{ id: 4, name: "Sarah Wilson", role: "member", status: "online" }
|
||||||
|
],
|
||||||
|
vehicles: [
|
||||||
|
{ id: 1, name: "Transport Truck", type: "Vehicle", value: 25000 },
|
||||||
|
{ id: 2, name: "Patrol Car", type: "Vehicle", value: 15000 }
|
||||||
|
],
|
||||||
|
equipment: [
|
||||||
|
{ id: 1, name: "Combat Gear Set", type: "Equipment", value: 5000 },
|
||||||
|
{ id: 3, name: "Radio Equipment", type: "Equipment", value: 3000 }
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{ id: 1, name: "Main Base", type: "Property", value: 100000 },
|
||||||
|
{ id: 2, name: "Storage Facility", type: "Property", value: 50000 }
|
||||||
|
],
|
||||||
|
supplies: [
|
||||||
|
{ id: 1, name: "Medical Supplies", type: "Supply", value: 2000, quantity: 50 },
|
||||||
|
{ id: 2, name: "Ammunition", type: "Supply", value: 5000, quantity: 1000 },
|
||||||
|
{ id: 3, name: "Food Rations", type: "Supply", value: 1000, quantity: 100 },
|
||||||
|
{ id: 4, name: "Repair Kits", type: "Supply", value: 3000, quantity: 25 }
|
||||||
|
],
|
||||||
|
memos: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: "Weekly Mission Update",
|
||||||
|
content: "New assignments available in the northern sector. All teams please check your mission boards.",
|
||||||
|
author: "John Doe",
|
||||||
|
date: "2025-04-12T08:30:00",
|
||||||
|
priority: "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "Equipment Maintenance",
|
||||||
|
content: "Regular maintenance check required for all vehicles by end of week.",
|
||||||
|
author: "Jane Smith",
|
||||||
|
date: "2025-04-11T14:15:00",
|
||||||
|
priority: "medium"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize the organization interface
|
||||||
|
function initializeOrg() {
|
||||||
|
updateOrgInfo();
|
||||||
|
updateMembers();
|
||||||
|
updateAssets();
|
||||||
|
updateTransactions();
|
||||||
|
updateMemos();
|
||||||
|
setupMemoControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update organization info in the header
|
||||||
|
function updateOrgInfo() {
|
||||||
|
document.getElementById('orgName').textContent = orgData.name;
|
||||||
|
document.getElementById('orgReputation').textContent = orgData.reputation.toLocaleString();
|
||||||
|
document.getElementById('orgFunds').textContent = `$${orgData.funds.toLocaleString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update members list
|
||||||
|
function updateMembers() {
|
||||||
|
const membersList = document.getElementById('membersList');
|
||||||
|
membersList.innerHTML = '';
|
||||||
|
|
||||||
|
orgData.members.forEach(member => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'member-item';
|
||||||
|
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="member-info">
|
||||||
|
<span class="member-status status-${member.status}"></span>
|
||||||
|
<span>${member.name}</span>
|
||||||
|
<span class="member-role role-${member.role}">${member.role}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
membersList.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update assets lists (vehicles, equipment, properties)
|
||||||
|
function updateAssets() {
|
||||||
|
// Update vehicles
|
||||||
|
const vehiclesList = document.getElementById('vehiclesList');
|
||||||
|
vehiclesList.innerHTML = '';
|
||||||
|
orgData.vehicles.forEach(vehicle => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'asset-item';
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="asset-info">
|
||||||
|
<span>${vehicle.name}</span>
|
||||||
|
<span class="asset-type">${vehicle.type}</span>
|
||||||
|
</div>
|
||||||
|
<span class="asset-value">$${vehicle.value.toLocaleString()}</span>
|
||||||
|
`;
|
||||||
|
vehiclesList.appendChild(li);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update equipment
|
||||||
|
const equipmentList = document.getElementById('equipmentList');
|
||||||
|
equipmentList.innerHTML = '';
|
||||||
|
orgData.equipment.forEach(item => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'asset-item';
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="asset-info">
|
||||||
|
<span>${item.name}</span>
|
||||||
|
<span class="asset-type">${item.type}</span>
|
||||||
|
</div>
|
||||||
|
<span class="asset-value">$${item.value.toLocaleString()}</span>
|
||||||
|
`;
|
||||||
|
equipmentList.appendChild(li);
|
||||||
|
}); // Update properties
|
||||||
|
const propertiesList = document.getElementById('propertiesList');
|
||||||
|
propertiesList.innerHTML = '';
|
||||||
|
orgData.properties.forEach(property => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'asset-item';
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="asset-info">
|
||||||
|
<span>${property.name}</span>
|
||||||
|
<span class="asset-type">${property.type}</span>
|
||||||
|
</div>
|
||||||
|
<span class="asset-value">$${property.value.toLocaleString()}</span>
|
||||||
|
`;
|
||||||
|
propertiesList.appendChild(li);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update supplies
|
||||||
|
const suppliesList = document.getElementById('suppliesList');
|
||||||
|
suppliesList.innerHTML = '';
|
||||||
|
orgData.supplies.forEach(supply => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'asset-item';
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="asset-info">
|
||||||
|
<span>${supply.name}</span>
|
||||||
|
<div class="supply-details">
|
||||||
|
<span class="asset-type">${supply.type}</span>
|
||||||
|
<span class="supply-quantity">Qty: ${supply.quantity}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="asset-value">$${supply.value.toLocaleString()}</span>
|
||||||
|
`;
|
||||||
|
suppliesList.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update transactions list
|
||||||
|
function updateTransactions() {
|
||||||
|
const transactionsList = document.getElementById('transactionsList');
|
||||||
|
transactionsList.innerHTML = '';
|
||||||
|
|
||||||
|
orgData.transactions.forEach(transaction => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'transaction-item';
|
||||||
|
|
||||||
|
const date = new Date(transaction.date);
|
||||||
|
const formattedDate = date.toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
|
||||||
|
const amountClass = transaction.amount >= 0 ? 'amount-positive' : 'amount-negative';
|
||||||
|
const prefix = transaction.amount >= 0 ? '+' : '';
|
||||||
|
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="transaction-info">
|
||||||
|
<div>${transaction.description}</div>
|
||||||
|
<div class="transaction-date">${formattedDate}</div>
|
||||||
|
</div>
|
||||||
|
<span class="transaction-amount ${amountClass}">${prefix}$${Math.abs(transaction.amount).toLocaleString()}</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
transactionsList.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update memos list
|
||||||
|
function updateMemos() {
|
||||||
|
const memosList = document.getElementById('memosList');
|
||||||
|
memosList.innerHTML = '';
|
||||||
|
|
||||||
|
orgData.memos.forEach(memo => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'memo-item';
|
||||||
|
|
||||||
|
const date = new Date(memo.date);
|
||||||
|
const formattedDate = date.toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="memo-header">
|
||||||
|
<div class="memo-title">${memo.title}</div>
|
||||||
|
<div class="memo-metadata">
|
||||||
|
<span class="memo-author">by ${memo.author}</span>
|
||||||
|
<span class="memo-date">${formattedDate}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="memo-content">${memo.content}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
memosList.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up memo controls and dialog
|
||||||
|
function setupMemoControls() {
|
||||||
|
const addMemoBtn = document.getElementById('addMemoBtn');
|
||||||
|
|
||||||
|
addMemoBtn.addEventListener('click', () => {
|
||||||
|
showMemoDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show memo creation dialog
|
||||||
|
function showMemoDialog() {
|
||||||
|
const dialog = document.createElement('div');
|
||||||
|
dialog.className = 'memo-dialog';
|
||||||
|
|
||||||
|
dialog.innerHTML = `
|
||||||
|
<form class="memo-form" id="memoForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="memoTitle">Title</label><br/>
|
||||||
|
<input type="text" id="memoTitle" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="memoContent">Content</label><br/>
|
||||||
|
<textarea id="memoContent" required></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="memo-form-buttons">
|
||||||
|
<button type="button" class="cancel-btn" id="cancelMemo">Cancel</button>
|
||||||
|
<button type="submit" class="submit-btn">Add Memo</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.className = 'memo-dialog-overlay';
|
||||||
|
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
document.body.appendChild(dialog);
|
||||||
|
|
||||||
|
const form = dialog.querySelector('#memoForm');
|
||||||
|
const cancelBtn = dialog.querySelector('#cancelMemo');
|
||||||
|
|
||||||
|
form.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const newMemo = {
|
||||||
|
id: orgData.memos.length + 1,
|
||||||
|
title: document.getElementById('memoTitle').value,
|
||||||
|
content: document.getElementById('memoContent').value,
|
||||||
|
author: orgData.members.find(m => m.role === 'owner').name,
|
||||||
|
date: new Date().toISOString(),
|
||||||
|
priority: "normal"
|
||||||
|
};
|
||||||
|
|
||||||
|
orgData.memos.unshift(newMemo);
|
||||||
|
updateMemos();
|
||||||
|
|
||||||
|
closeMemoDialog(dialog, overlay);
|
||||||
|
});
|
||||||
|
|
||||||
|
cancelBtn.addEventListener('click', () => {
|
||||||
|
closeMemoDialog(dialog, overlay);
|
||||||
|
});
|
||||||
|
|
||||||
|
overlay.addEventListener('click', () => {
|
||||||
|
closeMemoDialog(dialog, overlay);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close memo dialog
|
||||||
|
function closeMemoDialog(dialog, overlay) {
|
||||||
|
document.body.removeChild(dialog);
|
||||||
|
document.body.removeChild(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize when DOM is loaded
|
||||||
|
document.addEventListener('DOMContentLoaded', initializeOrg);
|
359
addons/org/ui/_site/styles.css
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item i {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sections-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make memo section span full width */
|
||||||
|
.section-tile.memo-section {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
aspect-ratio: unset;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-tile {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
padding: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-tile:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-tile h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-list, .asset-list {
|
||||||
|
list-style: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-item, .asset-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-item:last-child, .asset-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-info, .asset-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-role {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-owner {
|
||||||
|
background-color: #ffd700;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-admin {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-member {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-type {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supply-details {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supply-quantity {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #28a745;
|
||||||
|
background: rgba(40, 167, 69, 0.1);
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-value {
|
||||||
|
color: #007bff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-list {
|
||||||
|
list-style: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-description {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-date {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-amount {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-positive {
|
||||||
|
color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-negative {
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-controls {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-list {
|
||||||
|
list-style: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-item {
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-metadata {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-author {
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-content {
|
||||||
|
color: #444;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-dialog {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: white;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
z-index: 1000;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-form input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-form textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100px;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memo-form-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background-color: #5a6268;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-status {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-online {
|
||||||
|
background-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-offline {
|
||||||
|
background-color: #dc3545;
|
||||||
|
}
|
@ -11,13 +11,13 @@
|
|||||||
* 0: Class Name <STRING> - The classname of the item to purchase
|
* 0: Class Name <STRING> - The classname of the item to purchase
|
||||||
* 1: Price <NUMBER> - The price of the item
|
* 1: Price <NUMBER> - The price of the item
|
||||||
* 2: Config Type <STRING> - The type of config ("item", "weapon", "magazine", "backpack")
|
* 2: Config Type <STRING> - The type of config ("item", "weapon", "magazine", "backpack")
|
||||||
* 3: Item Type <STRING> - The type of item for locker storage
|
* 3: Item Type <STRING> - The type of item for locker storage ("backpack", "facewear", "headgear", "hmd", "item", "magazine", "uniform", "vest", "weapon")
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
* None
|
* None
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* ["arifle_MX_F", 1000, "weapon", "primary"] call forge_store_fnc_buyItem
|
* ["arifle_MX_F", 1000, "weapon", "weapon"] call forge_store_fnc_buyItem
|
||||||
*
|
*
|
||||||
* Public: No
|
* Public: No
|
||||||
*/
|
*/
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
* Arguments:
|
* Arguments:
|
||||||
* 0: Class Name <STRING> - The classname of the vehicle to purchase
|
* 0: Class Name <STRING> - The classname of the vehicle to purchase
|
||||||
* 1: Price <NUMBER> - The price of the vehicle
|
* 1: Price <NUMBER> - The price of the vehicle
|
||||||
* 2: Vehicle Type <STRING> - The type of vehicle ("car", "air", "ship", "tank")
|
* 2: Vehicle Type <STRING> - The type of vehicle ("car", "armor", "heli", "plane", "naval", "static")
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
* None
|
* None
|
||||||
|
@ -23,6 +23,7 @@ params ["_price"];
|
|||||||
|
|
||||||
private _paymentData = GVAR(activePayment);
|
private _paymentData = GVAR(activePayment);
|
||||||
private _payment = call compile _paymentData;
|
private _payment = call compile _paymentData;
|
||||||
|
private _store = nil;
|
||||||
|
|
||||||
scopeName "main";
|
scopeName "main";
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ if (count _payment > 3) then {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (_payment select 0 == "Organization") then {
|
if (_payment select 0 == "Organization") then {
|
||||||
private _store = call EFUNC(org,verifyOrgStore);
|
_store = call EFUNC(org,verifyOrgStore);
|
||||||
private _org = _store call ["getOrg", []];
|
private _org = _store call ["getOrg", []];
|
||||||
private _ownerUID = _org get "owner";
|
private _ownerUID = _org get "owner";
|
||||||
|
|
||||||
@ -49,7 +50,6 @@ if (_payment select 0 == "Organization") then {
|
|||||||
private _varType = _payment select 2;
|
private _varType = _payment select 2;
|
||||||
private _balance = switch (_varType) do {
|
private _balance = switch (_varType) do {
|
||||||
case "organization": {
|
case "organization": {
|
||||||
private _store = call EFUNC(org,verifyOrgStore);
|
|
||||||
_store call ["getFunds", []];
|
_store call ["getFunds", []];
|
||||||
};
|
};
|
||||||
case "player": { player getVariable [_payment select 1, 0] };
|
case "player": { player getVariable [_payment select 1, 0] };
|
||||||
@ -64,7 +64,6 @@ if (_balance < _price) exitWith {
|
|||||||
|
|
||||||
switch (_varType) do {
|
switch (_varType) do {
|
||||||
case "organization": {
|
case "organization": {
|
||||||
private _store = call EFUNC(org,verifyOrgStore);
|
|
||||||
_store call ["updateFunds", -_price];
|
_store call ["updateFunds", -_price];
|
||||||
};
|
};
|
||||||
case "player": {
|
case "player": {
|
||||||
|
150
addons/store/ui/_site/data/categories.json
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
{
|
||||||
|
"paymentMethods": [
|
||||||
|
{
|
||||||
|
"id": "cash",
|
||||||
|
"name": "Cash",
|
||||||
|
"icon": "💵"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bank",
|
||||||
|
"name": "Bank",
|
||||||
|
"icon": "🏦"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Weapons": {
|
||||||
|
"icon": "",
|
||||||
|
"subcategories": {
|
||||||
|
"Rifles": {
|
||||||
|
"icon": "",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Combat Rifle",
|
||||||
|
"price": 1500,
|
||||||
|
"image": "placeholder.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Sniper Rifle",
|
||||||
|
"price": 2500,
|
||||||
|
"image": "placeholder.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Pistols": {
|
||||||
|
"icon": "",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Tactical Pistol",
|
||||||
|
"price": 800,
|
||||||
|
"image": "placeholder.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Equipment": {
|
||||||
|
"icon": "",
|
||||||
|
"subcategories": {
|
||||||
|
"Vests": {
|
||||||
|
"icon": "",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "Tactical Vest",
|
||||||
|
"price": 800,
|
||||||
|
"image": "placeholder.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Headwear": {
|
||||||
|
"icon": "",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Combat Helmet",
|
||||||
|
"price": 1200,
|
||||||
|
"image": "placeholder.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Facewear": {
|
||||||
|
"icon": "",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "Balaclava",
|
||||||
|
"price": 500,
|
||||||
|
"image": "placeholder.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Hmd": {
|
||||||
|
"icon": "",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "Night Vision Goggles",
|
||||||
|
"price": 2000,
|
||||||
|
"image": "placeholder.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Backpacks": {
|
||||||
|
"icon": "",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "Combat Backpack",
|
||||||
|
"price": 450,
|
||||||
|
"image": "placeholder.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Uniforms": {
|
||||||
|
"icon": "",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "Combat Fatigues",
|
||||||
|
"price": 1000,
|
||||||
|
"image": "placeholder.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Vehicles": {
|
||||||
|
"icon": "",
|
||||||
|
"subcategories": {
|
||||||
|
"Wheeled": {
|
||||||
|
"icon": "",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"name": "Transport Vehicle",
|
||||||
|
"price": 25000,
|
||||||
|
"image": "placeholder.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Supplies": {
|
||||||
|
"icon": "",
|
||||||
|
"subcategories": {
|
||||||
|
"Medical": {
|
||||||
|
"icon": "",
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"name": "First Aid Kit",
|
||||||
|
"price": 300,
|
||||||
|
"image": "placeholder.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
addons/store/ui/_site/index.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>FORGE - GMS</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<script src="script.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header-content">
|
||||||
|
<h1>General Military Surplus</h1>
|
||||||
|
<div class="payment-method">
|
||||||
|
<select id="paymentMethod" class="payment-select">
|
||||||
|
<option value="" disabled selected>Select Payment Method</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="container">
|
||||||
|
<!-- Content will be dynamically populated -->
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
addons/store/ui/_site/public/fdic.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
addons/store/ui/_site/public/fdic_co.paa
Normal file
BIN
addons/store/ui/_site/public/fms.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
addons/store/ui/_site/public/fms_co.paa
Normal file
BIN
addons/store/ui/_site/public/gms.png
Normal file
After Width: | Height: | Size: 109 KiB |
BIN
addons/store/ui/_site/public/gms_co.paa
Normal file
174
addons/store/ui/_site/script.js
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
// Store data will be loaded from JSON
|
||||||
|
let categories = null;
|
||||||
|
let currentCategory = null;
|
||||||
|
let currentSubcategory = null;
|
||||||
|
let selectedPaymentMethod = null;
|
||||||
|
|
||||||
|
function initializePaymentMethods() {
|
||||||
|
const paymentSelect = document.getElementById('paymentMethod');
|
||||||
|
paymentSelect.innerHTML = `
|
||||||
|
<option value="" disabled ${!selectedPaymentMethod ? 'selected' : ''}>Select Payment Method</option>
|
||||||
|
${categories.paymentMethods.map(method => `
|
||||||
|
<option value="${method.id}" ${method.id === selectedPaymentMethod ? 'selected' : ''}>
|
||||||
|
${method.icon} ${method.name}
|
||||||
|
</option>
|
||||||
|
`).join('')}
|
||||||
|
`;
|
||||||
|
paymentSelect.addEventListener('change', (e) => {
|
||||||
|
selectedPaymentMethod = e.target.value;
|
||||||
|
updateBuyButtons();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBuyButtons() {
|
||||||
|
const buyButtons = document.querySelectorAll('.buy-btn');
|
||||||
|
buyButtons.forEach(button => {
|
||||||
|
button.disabled = !selectedPaymentMethod;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleQuantityChange(productId, change, quantityDisplay) {
|
||||||
|
const currentQty = parseInt(quantityDisplay.textContent);
|
||||||
|
const newQty = Math.max(1, currentQty + change);
|
||||||
|
quantityDisplay.textContent = newQty;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePurchase(product, quantity) {
|
||||||
|
if (!selectedPaymentMethod) {
|
||||||
|
alert('Please select a payment method first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = product.price * quantity;
|
||||||
|
alert(`Purchase Summary:
|
||||||
|
Product: ${product.name}
|
||||||
|
Quantity: ${quantity}
|
||||||
|
Total: $${total.toLocaleString()}
|
||||||
|
Payment Method: ${categories.paymentMethods.find(m => m.id === selectedPaymentMethod).name}`);
|
||||||
|
// Here you would typically integrate with your game's purchasing system
|
||||||
|
}
|
||||||
|
|
||||||
|
function showMainCategories() {
|
||||||
|
const mainContent = document.querySelector('main');
|
||||||
|
currentCategory = null;
|
||||||
|
currentSubcategory = null;
|
||||||
|
|
||||||
|
mainContent.innerHTML = `
|
||||||
|
<div class="categories-grid"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const categoriesGrid = document.querySelector('.categories-grid');
|
||||||
|
Object.entries(categories)
|
||||||
|
.filter(([key]) => key !== 'paymentMethods')
|
||||||
|
.forEach(([categoryName, categoryData]) => {
|
||||||
|
const categoryTile = document.createElement('button');
|
||||||
|
categoryTile.className = 'category-tile';
|
||||||
|
categoryTile.innerHTML = `
|
||||||
|
<span class="icon">${categoryData.icon}</span>
|
||||||
|
<span class="name">${categoryName}</span>
|
||||||
|
`;
|
||||||
|
categoryTile.addEventListener('click', () => showSubcategories(categoryName));
|
||||||
|
categoriesGrid.appendChild(categoryTile);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSubcategories(categoryName) {
|
||||||
|
const mainContent = document.querySelector('main');
|
||||||
|
currentCategory = categoryName;
|
||||||
|
currentSubcategory = null;
|
||||||
|
|
||||||
|
mainContent.innerHTML = `
|
||||||
|
<button class="back-button">← Back to Categories</button>
|
||||||
|
<h2>${categoryName}</h2>
|
||||||
|
<div class="subcategories-grid"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const subcategoriesGrid = document.querySelector('.subcategories-grid');
|
||||||
|
const backButton = document.querySelector('.back-button');
|
||||||
|
backButton.addEventListener('click', showMainCategories);
|
||||||
|
|
||||||
|
Object.entries(categories[categoryName].subcategories).forEach(([subCategoryName, subCategoryData]) => {
|
||||||
|
const subCategoryTile = document.createElement('button');
|
||||||
|
subCategoryTile.className = 'subcategory-tile';
|
||||||
|
subCategoryTile.innerHTML = `
|
||||||
|
<span class="icon">${subCategoryData.icon}</span>
|
||||||
|
<span class="name">${subCategoryName}</span>
|
||||||
|
`;
|
||||||
|
subCategoryTile.addEventListener('click', () => showProducts(categoryName, subCategoryName));
|
||||||
|
subcategoriesGrid.appendChild(subCategoryTile);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showProducts(categoryName, subCategoryName) {
|
||||||
|
const mainContent = document.querySelector('main');
|
||||||
|
currentSubcategory = subCategoryName;
|
||||||
|
|
||||||
|
mainContent.innerHTML = `
|
||||||
|
<button class="back-button">← Back to ${categoryName}</button>
|
||||||
|
<h2>${categoryName} > ${subCategoryName}</h2>
|
||||||
|
<div class="products-grid"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const productsGrid = document.querySelector('.products-grid');
|
||||||
|
const backButton = document.querySelector('.back-button');
|
||||||
|
backButton.addEventListener('click', () => showSubcategories(categoryName));
|
||||||
|
|
||||||
|
const products = categories[categoryName].subcategories[subCategoryName].products;
|
||||||
|
products.forEach(product => {
|
||||||
|
const productCard = document.createElement('div');
|
||||||
|
productCard.className = 'product-card';
|
||||||
|
productCard.innerHTML = `
|
||||||
|
<div class="product-image"></div>
|
||||||
|
<div class="product-info">
|
||||||
|
<h3 class="product-name">${product.name}</h3>
|
||||||
|
<p class="product-price">$${product.price.toLocaleString()}</p>
|
||||||
|
<div class="product-controls">
|
||||||
|
<div class="quantity-controls">
|
||||||
|
<button class="quantity-btn minus">-</button>
|
||||||
|
<span class="quantity-display">1</span>
|
||||||
|
<button class="quantity-btn plus">+</button>
|
||||||
|
</div>
|
||||||
|
<button class="buy-btn" ${!selectedPaymentMethod ? 'disabled' : ''}>
|
||||||
|
Buy Now
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const quantityDisplay = productCard.querySelector('.quantity-display');
|
||||||
|
const minusBtn = productCard.querySelector('.quantity-btn.minus');
|
||||||
|
const plusBtn = productCard.querySelector('.quantity-btn.plus');
|
||||||
|
const buyBtn = productCard.querySelector('.buy-btn');
|
||||||
|
|
||||||
|
minusBtn.addEventListener('click', () => handleQuantityChange(product.id, -1, quantityDisplay));
|
||||||
|
plusBtn.addEventListener('click', () => handleQuantityChange(product.id, 1, quantityDisplay));
|
||||||
|
buyBtn.addEventListener('click', () => handlePurchase(product, parseInt(quantityDisplay.textContent)));
|
||||||
|
|
||||||
|
productsGrid.appendChild(productCard);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the store
|
||||||
|
async function initializeStore() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:8000/data/categories.json');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to load store data');
|
||||||
|
}
|
||||||
|
categories = await response.json();
|
||||||
|
initializePaymentMethods();
|
||||||
|
showMainCategories();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading store data:', error);
|
||||||
|
document.querySelector('main').innerHTML = `
|
||||||
|
<div class="error-message">
|
||||||
|
<h2>Error Loading Store Data</h2>
|
||||||
|
<p>Please try again later.</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initializeStore();
|
||||||
|
});
|
16
addons/store/ui/_site/server.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import http.server
|
||||||
|
import socketserver
|
||||||
|
|
||||||
|
PORT = 8000
|
||||||
|
DIRECTORY = '.'
|
||||||
|
|
||||||
|
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, directory=DIRECTORY, **kwargs)
|
||||||
|
|
||||||
|
with socketserver.TCPServer(("", PORT), Handler) as httpd:
|
||||||
|
print(f"Serving at http://localhost:{PORT}")
|
||||||
|
try:
|
||||||
|
httpd.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nServer stopped.")
|
200
addons/store/ui/_site/styles.css
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categories-grid, .subcategories-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tile, .subcategory-tile {
|
||||||
|
background-color: #444;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tile:hover, .subcategory-tile:hover {
|
||||||
|
background-color: #666;
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tile.active, .subcategory-tile.active {
|
||||||
|
background-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tile i, .subcategory-tile i {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
background-color: #444;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button:hover {
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.products-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 256px;
|
||||||
|
object-fit: cover;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-info {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-name {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #007bff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-select {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-btn {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-btn:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-display {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
min-width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-btn {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-btn:hover {
|
||||||
|
background-color: #218838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-btn:disabled {
|
||||||
|
background-color: #6c757d;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|