ProductionGame/frontend/js/ui/contractsView.js

180 lines
8.2 KiB
JavaScript

// Production - The Idle Game: B2B Procurement Contracts Board
// Fine-grained DOM updates without innerHTML teardowns
import { getProduct } from '../data/products.js';
import { getClient } from '../data/contracts.js';
import { fmtCurrency, fmtNum, fmtTime } from '../core/formatters.js';
let currentMountedContractUids = '';
export function mountContractsBoard(container, state, onAction) {
const contracts = state.activeContracts || [];
currentMountedContractUids = contracts.map(c => c.uid).join(',');
let html = `
<div class="contracts-header-pane">
<div class="contracts-header-info">
<h2 class="view-title">
<iconify-icon icon="lucide:scroll-text" class="title-icon"></iconify-icon>
B2B Corporate Procurement Tenders
</h2>
<p class="view-subtitle">
Supply high-priority commodity shipments to corporate clients for massive cash injections and corporate prestige.
</p>
</div>
<div class="contracts-meta-badge">
<span>Ready to Deliver:</span>
<strong id="contract-stats-ready" class="font-mono text-profit">${state.readyContractsCount || 0}</strong>
<span style="margin-left: 8px; border-left: 1px solid var(--border-color); padding-left: 8px;">Completed:</span>
<strong id="contract-stats-completed" class="font-mono">${state.stats.contractsCompleted || 0}</strong>
</div>
</div>
<div class="contracts-grid">
`;
if (contracts.length === 0) {
html += `
<div class="empty-contracts-card">
<iconify-icon icon="lucide:clock" class="empty-icon"></iconify-icon>
<h3>Awaiting New Tenders</h3>
<p>Corporate procurement agents are preparing new commodity RFPs...</p>
</div>
`;
}
for (const c of contracts) {
const client = getClient(c.clientId);
const product = getProduct(c.productId);
const currentStock = state.products[c.productId]?.stock || 0;
const canFulfill = currentStock >= c.targetAmount;
const progressPct = Math.min(100, Math.floor((currentStock / c.targetAmount) * 100));
html += `
<div class="contract-card ${canFulfill ? 'ready-to-fulfill' : ''}" id="contract-${c.uid}">
<div class="contract-client-row">
<div class="client-brand">
<div class="client-avatar">
<iconify-icon icon="${client.icon}"></iconify-icon>
</div>
<div>
<h4 class="client-name">${client.name}</h4>
<span class="client-badge">${client.badge}</span>
</div>
</div>
<div class="contract-timer font-mono ${c.remainingSeconds < 60 ? 'timer-urgent' : ''}" id="timer-box-${c.uid}">
<iconify-icon icon="lucide:timer"></iconify-icon>
<span id="timer-val-${c.uid}">${fmtTime(c.remainingSeconds)}</span>
</div>
</div>
<div class="contract-body">
<h3 class="contract-title">${c.title}</h3>
<div class="contract-req-row">
<div class="contract-req-item">
<span class="req-label">Required Commodity:</span>
<div class="req-val-wrap">
<iconify-icon icon="${product?.icon || 'lucide:box'}" style="color: ${product?.color || '#2563eb'};"></iconify-icon>
<strong>${product?.name}</strong>
</div>
</div>
<div class="contract-req-progress font-mono">
<span id="c-curr-stock-${c.uid}">${fmtNum(currentStock, 0)}</span> / <strong>${fmtNum(c.targetAmount, 0)}</strong> ${product?.unit || 'units'}
</div>
</div>
<div class="contract-progress-track">
<div class="contract-progress-fill" id="c-fill-${c.uid}" style="width: ${progressPct}%;"></div>
</div>
<div class="contract-payout-box">
<div class="payout-col">
<span class="payout-label">Contract Payout:</span>
<span class="payout-val font-mono">${fmtCurrency(c.rewardCash)}</span>
</div>
<div class="payout-col">
<span class="payout-label">Corporate Rep:</span>
<span class="payout-val font-mono">+${c.rewardRep} Rep</span>
</div>
</div>
</div>
<div class="contract-footer">
<button class="btn-primary-corp btn-fulfill-contract" id="c-btn-${c.uid}" data-uid="${c.uid}" ${canFulfill ? '' : 'disabled'}>
<iconify-icon icon="lucide:check-check"></iconify-icon>
<span id="c-btn-text-${c.uid}">${canFulfill ? 'Deliver Shipment & Collect Payout' : `Fulfill Tender (Need ${fmtNum(c.targetAmount - currentStock, 0)} more)`}</span>
</button>
</div>
</div>
`;
}
html += `</div>`;
container.innerHTML = html;
container.querySelectorAll('.btn-fulfill-contract').forEach(btn => {
btn.onclick = () => onAction({ type: 'FULFILL_CONTRACT', uid: btn.dataset.uid });
});
}
export function updateContractsBoard(container, state, onAction) {
const contracts = state.activeContracts || [];
const currentUids = contracts.map(c => c.uid).join(',');
// If active contracts changed (added, completed, expired), remount cleanly
if (currentUids !== currentMountedContractUids) {
mountContractsBoard(container, state, onAction);
return;
}
const elStatsReady = document.getElementById('contract-stats-ready');
if (elStatsReady) {
const val = String(state.readyContractsCount || 0);
if (elStatsReady.textContent !== val) elStatsReady.textContent = val;
}
const elStatsCompleted = document.getElementById('contract-stats-completed');
if (elStatsCompleted) {
const val = String(state.stats?.contractsCompleted || 0);
if (elStatsCompleted.textContent !== val) elStatsCompleted.textContent = val;
}
for (const c of contracts) {
const currentStock = state.products[c.productId]?.stock || 0;
const canFulfill = currentStock >= c.targetAmount;
const progressPct = Math.min(100, Math.floor((currentStock / c.targetAmount) * 100));
// Timer
const elTimerVal = document.getElementById(`timer-val-${c.uid}`);
if (elTimerVal) elTimerVal.textContent = fmtTime(c.remainingSeconds);
const elTimerBox = document.getElementById(`timer-box-${c.uid}`);
if (elTimerBox) {
elTimerBox.classList.toggle('timer-urgent', c.remainingSeconds < 60);
}
// Current Stock Text
const elCurrStock = document.getElementById(`c-curr-stock-${c.uid}`);
if (elCurrStock) elCurrStock.textContent = fmtNum(currentStock, 0);
// Progress bar
const elFill = document.getElementById(`c-fill-${c.uid}`);
if (elFill) elFill.style.width = `${progressPct}%`;
// Card fulfill class
const elCard = document.getElementById(`contract-${c.uid}`);
if (elCard) elCard.classList.toggle('ready-to-fulfill', canFulfill);
// Button disabled & text
const elBtn = document.getElementById(`c-btn-${c.uid}`);
if (elBtn) {
elBtn.disabled = !canFulfill;
const elBtnText = document.getElementById(`c-btn-text-${c.uid}`);
if (elBtnText) {
elBtnText.textContent = canFulfill
? 'Deliver Shipment & Collect Payout'
: `Fulfill Tender (Need ${fmtNum(c.targetAmount - currentStock, 0)} more)`;
}
}
}
}