diff --git a/client/src/ui/StocksPanel.ts b/client/src/ui/StocksPanel.ts new file mode 100644 index 0000000..e091628 --- /dev/null +++ b/client/src/ui/StocksPanel.ts @@ -0,0 +1,169 @@ +import type { StockpileLogEntry } from '@dflike/shared'; + +const MAX_LOG_ENTRIES = 50; + +export class StocksPanel { + private container: HTMLDivElement; + private summaryEl: HTMLDivElement; + private logEl: HTMLDivElement; + private emptyEl: HTMLDivElement; + private logEntries: StockpileLogEntry[] = []; + private _hasUnseen = false; + onUnseenChanged: ((hasUnseen: boolean) => void) | null = null; + + constructor() { + this.container = document.createElement('div'); + this.container.style.cssText = ` + display: flex; + flex-direction: column; + height: 100%; + gap: 8px; + `; + + // Summary section + const summaryLabel = document.createElement('div'); + summaryLabel.style.cssText = ` + color: #e0d0b0; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1px; + padding-bottom: 4px; + border-bottom: 1px solid #8878a8; + flex-shrink: 0; + `; + summaryLabel.textContent = 'Stockpiled Resources'; + this.container.appendChild(summaryLabel); + + this.summaryEl = document.createElement('div'); + this.summaryEl.style.cssText = ` + display: flex; + flex-direction: column; + gap: 2px; + flex-shrink: 0; + padding-bottom: 6px; + border-bottom: 1px solid #8878a8; + `; + this.container.appendChild(this.summaryEl); + + // Log section + const logLabel = document.createElement('div'); + logLabel.style.cssText = ` + color: #e0d0b0; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 1px; + flex-shrink: 0; + `; + logLabel.textContent = 'Activity'; + this.container.appendChild(logLabel); + + this.emptyEl = document.createElement('div'); + this.emptyEl.style.cssText = ` + color: #8878a8; + font-size: 10px; + text-align: center; + padding: 20px 0; + `; + this.emptyEl.textContent = 'No activity yet...'; + this.container.appendChild(this.emptyEl); + + this.logEl = document.createElement('div'); + this.logEl.style.cssText = ` + display: flex; + flex-direction: column; + gap: 2px; + overflow-y: auto; + flex: 1; + `; + this.container.appendChild(this.logEl); + } + + getElement(): HTMLDivElement { + return this.container; + } + + get hasUnseen(): boolean { + return this._hasUnseen; + } + + clearUnseen(): void { + this._hasUnseen = false; + this.onUnseenChanged?.(false); + } + + updateSummary(data: Record): void { + this.summaryEl.innerHTML = ''; + const items = Object.entries(data).filter(([, qty]) => qty > 0); + if (items.length === 0) { + const empty = document.createElement('div'); + empty.style.cssText = 'color: #8878a8; font-size: 10px; padding: 4px 0;'; + empty.textContent = 'Empty'; + this.summaryEl.appendChild(empty); + return; + } + for (const [itemId, qty] of items) { + const row = document.createElement('div'); + row.style.cssText = ` + display: flex; + justify-content: space-between; + font-size: 11px; + `; + const nameEl = document.createElement('span'); + nameEl.style.cssText = 'color: #a0a0c0; text-transform: capitalize;'; + nameEl.textContent = itemId.replace(/_/g, ' '); + const qtyEl = document.createElement('span'); + qtyEl.style.cssText = 'color: #e0d0b0; text-shadow: 1px 1px 0 rgba(0,0,0,0.5);'; + qtyEl.textContent = String(qty); + row.appendChild(nameEl); + row.appendChild(qtyEl); + this.summaryEl.appendChild(row); + } + } + + addLogEntry(entry: StockpileLogEntry): void { + this.logEntries.push(entry); + if (this.logEntries.length > MAX_LOG_ENTRIES) { + this.logEntries.shift(); + } + this.emptyEl.style.display = 'none'; + const el = this.createLogLine(entry); + this.logEl.insertBefore(el, this.logEl.firstChild); + + // Trim DOM if over limit + while (this.logEl.children.length > MAX_LOG_ENTRIES) { + this.logEl.removeChild(this.logEl.lastChild!); + } + + this._hasUnseen = true; + this.onUnseenChanged?.(true); + } + + loadHistory(entries: StockpileLogEntry[]): void { + this.logEntries = [...entries]; + this.logEl.innerHTML = ''; + if (entries.length === 0) { + this.emptyEl.style.display = ''; + return; + } + this.emptyEl.style.display = 'none'; + // Show newest first + for (let i = entries.length - 1; i >= 0; i--) { + this.logEl.appendChild(this.createLogLine(entries[i])); + } + } + + private createLogLine(entry: StockpileLogEntry): HTMLDivElement { + const el = document.createElement('div'); + el.style.cssText = ` + font-size: 9px; + color: #a0a0c0; + padding: 2px 0; + border-bottom: 1px solid #1c1450; + line-height: 1.5; + `; + const verb = entry.action === 'dropoff' ? 'dropped off' : 'took'; + const itemName = entry.itemId.replace(/_/g, ' '); + el.textContent = `${entry.npcName} ${verb} ${entry.quantity} ${itemName}`; + return el; + } +}