// 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(','); const repPool = state.reputation || 0; const allocs = state.repAllocations || { b2b: 0, clients: 0, brand: 0 }; const b2bRep = allocs.b2b || 0; const clientsRep = allocs.clients || 0; const brandRep = allocs.brand || 0; const totalAllocated = b2bRep + clientsRep + brandRep; const totalAccumulatedRep = repPool + totalAllocated; // Computed bonus percentages const b2bPayoutPct = (b2bRep * 0.5).toFixed(1); const apexYieldPct = (clientsRep * 0.2).toFixed(1); const titanDiscountPct = Math.min(50, clientsRep * 0.2).toFixed(1); const omniSpeedPct = (clientsRep * 0.15).toFixed(1); const vanguardCapPct = (clientsRep * 0.25).toFixed(1); const brandPricePct = (brandRep * 0.25).toFixed(1); let html = `

B2B Corporate Procurement Tenders

Supply high-priority commodity shipments to corporate clients for massive cash injections and corporate prestige.

Ready to Deliver: ${state.readyContractsCount || 0} Completed: ${state.stats?.contractsCompleted || 0}

Corporate Standing & Reputation Hub

Distribute corporate reputation earned from fulfilled contracts across 3 strategic branches. Points can be reallocated at any time.

Available Rep Pool ${fmtNum(repPool, 0)}
Accumulated: ${fmtNum(totalAccumulatedRep, 0)}

B2B Procurement Standing

Model 1: Tender Optimization
${fmtNum(b2bRep, 0)} Rep

Increases cash payouts on all delivered B2B tenders by establishing premier vendor standing.

Contract Cash Bonus: +${b2bPayoutPct}%

Client Strategic Relations

Model 2: MegaCorp Synergies
${fmtNum(clientsRep, 0)} Rep

Deepens industrial partnerships with the 4 MegaCorps to unlock multifaceted operational synergies.

Apex Raw Yield +${apexYieldPct}%
Titan Line Discount -${titanDiscountPct}%
Omni Cycle Speed +${omniSpeedPct}%
Vanguard Fleet Flow +${vanguardCapPct}%

Wholesale Brand Premium

Model 3: Market Margin Power
${fmtNum(brandRep, 0)} Rep

Builds prestigious global brand equity, boosting open-market wholesale prices across all logistics fleets.

Wholesale Price Premium: +${brandPricePct}%
`; if (contracts.length === 0) { html += `

Awaiting New Tenders

Corporate procurement agents are preparing new commodity RFPs...

`; } 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 += `

${client.name}

${client.badge}
${fmtTime(c.remainingSeconds)}

${c.title}

Required Commodity:
${product?.name}
${fmtNum(currentStock, 0)} / ${fmtNum(c.targetAmount, 0)} ${product?.unit || 'units'}
Contract Payout: ${fmtCurrency(c.rewardCash)}
Corporate Rep: +${c.rewardRep} Rep
`; } html += `
`; container.innerHTML = html; container.querySelectorAll('.btn-fulfill-contract').forEach(btn => { btn.onclick = () => onAction({ type: 'FULFILL_CONTRACT', uid: btn.dataset.uid }); }); // Rep Hub Allocation Control Listeners container.querySelectorAll('.btn-rep-ctrl').forEach(btn => { btn.onclick = () => onAction({ type: 'ALLOCATE_REP', department: btn.dataset.dept, delta: btn.dataset.delta }); }); const btnReset = container.querySelector('#btn-rep-reset'); if (btnReset) { btnReset.onclick = () => onAction({ type: 'ALLOCATE_REP', delta: 'reset' }); } } 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; } // Update Rep Pool & Allocations Live DOM const repPool = state.reputation || 0; const allocs = state.repAllocations || { b2b: 0, clients: 0, brand: 0 }; const b2bRep = allocs.b2b || 0; const clientsRep = allocs.clients || 0; const brandRep = allocs.brand || 0; const totalAllocated = b2bRep + clientsRep + brandRep; const elRepPool = document.getElementById('rep-pool-val'); if (elRepPool) { const poolTxt = fmtNum(repPool, 0); if (elRepPool.textContent !== poolTxt) elRepPool.textContent = poolTxt; } const elResetBtn = document.getElementById('btn-rep-reset'); if (elResetBtn) { elResetBtn.disabled = totalAllocated <= 0; } // Model 1: B2B const elBadgeB2B = document.getElementById('rep-alloc-badge-b2b'); if (elBadgeB2B) elBadgeB2B.textContent = `${fmtNum(b2bRep, 0)} Rep`; const elBonusB2B = document.getElementById('rep-bonus-b2b'); if (elBonusB2B) elBonusB2B.textContent = `+${(b2bRep * 0.5).toFixed(1)}%`; // Model 2: Clients const elBadgeClients = document.getElementById('rep-alloc-badge-clients'); if (elBadgeClients) elBadgeClients.textContent = `${fmtNum(clientsRep, 0)} Rep`; const elBonusApex = document.getElementById('rep-bonus-apex'); if (elBonusApex) elBonusApex.textContent = `+${(clientsRep * 0.2).toFixed(1)}%`; const elBonusTitan = document.getElementById('rep-bonus-titan'); if (elBonusTitan) elBonusTitan.textContent = `-${Math.min(50, clientsRep * 0.2).toFixed(1)}%`; const elBonusOmni = document.getElementById('rep-bonus-omni'); if (elBonusOmni) elBonusOmni.textContent = `+${(clientsRep * 0.15).toFixed(1)}%`; const elBonusVanguard = document.getElementById('rep-bonus-vanguard'); if (elBonusVanguard) elBonusVanguard.textContent = `+${(clientsRep * 0.25).toFixed(1)}%`; // Model 3: Brand const elBadgeBrand = document.getElementById('rep-alloc-badge-brand'); if (elBadgeBrand) elBadgeBrand.textContent = `${fmtNum(brandRep, 0)} Rep`; const elBonusBrand = document.getElementById('rep-bonus-brand'); if (elBonusBrand) elBonusBrand.textContent = `+${(brandRep * 0.25).toFixed(1)}%`; // Rep Control Buttons Disabled State container.querySelectorAll('.btn-rep-ctrl').forEach(btn => { const dept = btn.dataset.dept; const delta = parseInt(btn.dataset.delta, 10); const currentDeptRep = allocs[dept] || 0; if (delta > 0) { btn.disabled = repPool < delta; } else if (delta < 0) { btn.disabled = currentDeptRep < Math.abs(delta); } }); 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)`; } } } }