From 18abfd3d086958233e73584427fddad0fac9c87d Mon Sep 17 00:00:00 2001 From: CAtlas0784 Date: Sat, 17 Oct 2026 15:33:47 +0700 Subject: [PATCH] feat(dashboard): replace weakness break coverage with Mini Battle Summary and Team Combat Stats --- css/styles.css | 10 ++- index.html | 73 +++++++++++------- js/app.js | 204 ++++++++++++++++++++++++++++--------------------- js/i18n.js | 28 ++++++- 4 files changed, 197 insertions(+), 118 deletions(-) diff --git a/css/styles.css b/css/styles.css index 8c65cc8..b3e0bae 100644 --- a/css/styles.css +++ b/css/styles.css @@ -2555,12 +2555,20 @@ input:checked + .slider:before { max-width: 100%; } -.synergy-metrics-grid { +.synergy-metrics-grid, +.mini-battle-metrics-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.5rem; } +@media (max-width: 640px) { + .synergy-metrics-grid, + .mini-battle-metrics-grid { + grid-template-columns: repeat(2, 1fr); + } +} + .synergy-metric-box { background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.07); diff --git a/index.html b/index.html index 896a49f..f2aa4f2 100644 --- a/index.html +++ b/index.html @@ -145,31 +145,60 @@ - -
+ +
- ⚔️ - วิเคราะห์ Synergy ทีม & จุดอ่อน + 💥 + สรุปการต่อสู้ล่าสุด & ดาเมจทีม - ครอบคลุม 0/7 ธาตุ -
-

วิเคราะห์การครอบคลุม Weakness Break และความพร้อมของอุปกรณ์ในทีม

- - -
-
- 🎯 การทำลายจุดอ่อน (Weakness Break Coverage) +
+ + ยังไม่มีข้อมูลการต่อสู้ + +
-
- +
+

+ สถิติดาเมจ, DPS และสัดส่วนการทำดาเมจของตัวละครในทีมจากการต่อสู้จริงล่าสุด +

+ + +
+
+
-
+
ดาเมจรวม
+
+
+
-
+
DPS รวม
+
+
+
-
+
Break / Super Break
+
+
+
-
+
เทิร์น / เวลา
- + +
+
+ 📊 สัดส่วนดาเมจสมาชิกในทีม (Damage Share) +
+
+ +
+
+ +
- 📊 ความพร้อมของอุปกรณ์และเลเวลเฉลี่ย + 🛡️ ความพร้อมของอุปกรณ์และเลเวลเฉลี่ย
@@ -190,18 +219,6 @@
- - -
-
- 💥 -
-
Damage Analyst (จับดาเมจจากในเกม)
-
ยังไม่มีข้อมูลการต่อสู้ล่าสุด
-
-
- -
diff --git a/js/app.js b/js/app.js index 1116f5f..a1bc33a 100644 --- a/js/app.js +++ b/js/app.js @@ -73,17 +73,19 @@ const el = { lineupContainer: document.getElementById('lineup-container'), toggleCreativeMode: document.getElementById('toggle-creative-mode'), badgeCreativeMode: document.getElementById('badge-creative-mode'), - // Team Combat Synergy - cardTeamSynergy: document.getElementById('card-team-synergy'), - badgeSynergyCoverage: document.getElementById('badge-synergy-coverage'), - synergyElementsContainer: document.getElementById('synergy-elements-container'), + // Mini Battle Summary & Team Stats + cardBattleSummary: document.getElementById('card-battle-summary'), + badgeMiniBattleStatus: document.getElementById('badge-mini-battle-status'), + miniTotalDmg: document.getElementById('mini-total-dmg'), + miniTeamDps: document.getElementById('mini-team-dps'), + miniBreakDmg: document.getElementById('mini-break-dmg'), + miniBattleTurns: document.getElementById('mini-battle-turns'), + miniBattleAvatarsList: document.getElementById('mini-battle-avatars-list'), + btnMiniViewFullAnalyst: document.getElementById('btn-mini-view-full-analyst'), synergyAvgLevel: document.getElementById('synergy-avg-level'), synergyAvgEidolon: document.getElementById('synergy-avg-eidolon'), synergyLcReady: document.getElementById('synergy-lc-ready'), synergyRelicReady: document.getElementById('synergy-relic-ready'), - synergyDamageBanner: document.getElementById('synergy-damage-banner'), - dashboardBattleTitle: document.getElementById('dashboard-battle-title'), - dashboardBattleSummary: document.getElementById('dashboard-battle-summary'), // Damage Analyst tabDamageAnalyst: document.getElementById('tab-damage-analyst'), @@ -1267,20 +1269,112 @@ function renderLineup(lineups) { } // ========================================== -// Team Combat Synergy & Weakness Break Analysis +// Mini Battle Summary & Team Stats // ========================================== -const HSR_ELEMENTS = [ - { key: 'Physical', name: 'Physical', nameTh: 'กายภาพ', color: '#cbd5e1' }, - { key: 'Fire', name: 'Fire', nameTh: 'ไฟ', color: '#ef4444' }, - { key: 'Ice', name: 'Ice', nameTh: 'น้ำแข็ง', color: '#38bdf8' }, - { key: 'Lightning', name: 'Lightning', nameTh: 'สายฟ้า', color: '#c084fc' }, - { key: 'Wind', name: 'Wind', nameTh: 'ลม', color: '#34d399' }, - { key: 'Quantum', name: 'Quantum', nameTh: 'ควอนตัม', color: '#818cf8' }, - { key: 'Imaginary', name: 'Imaginary', nameTh: 'จินตภาพ', color: '#fbbf24' } -]; +const ELEMENT_COLORS = { + Physical: '#cbd5e1', + Fire: '#ef4444', + Ice: '#38bdf8', + Lightning: '#c084fc', + Thunder: '#c084fc', + Wind: '#34d399', + Quantum: '#818cf8', + Imaginary: '#fbbf24' +}; + +function renderMiniBattleSummary() { + if (!el.miniBattleAvatarsList) return; + const report = analystState.currentReport; + + if (!report || !report.team_total_damage || !report.avatars || report.avatars.length === 0) { + if (el.badgeMiniBattleStatus) { + el.badgeMiniBattleStatus.textContent = t('miniNoBattleTitle'); + el.badgeMiniBattleStatus.style.background = 'rgba(156, 163, 175, 0.12)'; + el.badgeMiniBattleStatus.style.color = 'var(--text-muted)'; + el.badgeMiniBattleStatus.style.borderColor = 'rgba(156, 163, 175, 0.25)'; + } + if (el.miniTotalDmg) el.miniTotalDmg.textContent = '-'; + if (el.miniTeamDps) el.miniTeamDps.textContent = '-'; + if (el.miniBreakDmg) el.miniBreakDmg.textContent = '-'; + if (el.miniBattleTurns) el.miniBattleTurns.textContent = '-'; + + el.miniBattleAvatarsList.innerHTML = ` +
+
⚔️
+
${t('miniNoBattleTitle')}
+
+ ${t('miniNoBattleDesc')} +
+
+ + +
+
+ `; + return; + } + + // Active Report + const isWin = (report.end_status || 'WIN').toUpperCase() === 'WIN'; + if (el.badgeMiniBattleStatus) { + el.badgeMiniBattleStatus.textContent = isWin ? '✅ ชนะการต่อสู้' : `⚠️ สิ้นสุด (${report.end_status})`; + el.badgeMiniBattleStatus.style.background = isWin ? 'rgba(52, 211, 153, 0.15)' : 'rgba(239, 68, 68, 0.15)'; + el.badgeMiniBattleStatus.style.color = isWin ? 'var(--accent-green)' : '#f87171'; + el.badgeMiniBattleStatus.style.borderColor = isWin ? 'rgba(52, 211, 153, 0.4)' : 'rgba(239, 68, 68, 0.4)'; + } + + const durationSec = report.cost_time || 0; + const teamDps = durationSec > 0 ? (report.team_total_damage / durationSec) : 0; + let totalBreakDmg = 0; + (report.avatars || []).forEach(a => { + totalBreakDmg += (a.total_break_damage || 0); + }); + + if (el.miniTotalDmg) el.miniTotalDmg.textContent = formatNumber(report.team_total_damage); + if (el.miniTeamDps) el.miniTeamDps.textContent = `${formatNumber(Math.round(teamDps))}/s`; + if (el.miniBreakDmg) el.miniBreakDmg.textContent = formatNumber(totalBreakDmg); + if (el.miniBattleTurns) el.miniBattleTurns.textContent = `${report.total_battle_turns || 0} เทิร์น (${formatDuration(durationSec)})`; + + // Sort avatars by total damage descending + const sortedAvatars = [...report.avatars].sort((a, b) => (b.total_damage || 0) - (a.total_damage || 0)); + + el.miniBattleAvatarsList.innerHTML = sortedAvatars.map(av => { + const meta = getAvatarInfo(av.avatar_id); + const elemColor = ELEMENT_COLORS[meta.element] || '#38bdf8'; + const pct = Math.max(0, Math.min(100, av.damage_percent || 0)); + const iconUrl = getAvatarIconUrl(av.avatar_id); + const rankStr = av.rank !== undefined ? `E${av.rank}` : ''; + const lvlStr = av.level ? `Lv.${av.level}` : ''; + const metaSub = [lvlStr, rankStr, translatePath(meta.path)].filter(Boolean).join(' • '); + + return ` +
+
+
+ ${meta.name} +
+
${meta.name}
+
${metaSub}
+
+
+
+ ${formatNumber(av.total_damage)} + ${pct.toFixed(1)}% +
+
+
+
+
+
+ `; + }).join(''); +} function renderTeamSynergy(lineups) { - if (!el.synergyElementsContainer) return; const currentLineup = lineups || state.statusData?.lineups || DEFAULT_LINEUP; // Extract active character IDs @@ -1292,22 +1386,12 @@ function renderTeamSynergy(lineups) { } } - // 1. Weakness Break Coverage - const coveredElements = {}; - const roles = { - dps: [], // Destruction, Hunt, Erudition - specialist: [], // Remembrance, Elation - support: [], // Harmony, Nihility - sustain: [] // Preservation, Abundance - }; - let totalLevel = 0; let totalEidolon = 0; let lcCount = 0; let fullRelicCount = 0; activeIds.forEach(aid => { - const meta = getAvatarInfo(aid); const av = state.fullFreesrData?.avatars?.[aid]; const lvl = av?.level || 80; const rank = av?.data?.rank ?? (state.avatarsList.find(a => a.avatar_id === aid)?.rank ?? 0); @@ -1315,25 +1399,6 @@ function renderTeamSynergy(lineups) { totalLevel += lvl; totalEidolon += rank; - // Element coverage - const rawElem = normalizeElement(meta.rawElement || meta.element); - if (!coveredElements[rawElem]) { - coveredElements[rawElem] = []; - } - coveredElements[rawElem].push(meta.name); - - // Path / Role - const rawPath = normalizePath(meta.rawPath || meta.path); - if (['Destruction', 'Hunt', 'Erudition'].includes(rawPath)) { - roles.dps.push(meta.name); - } else if (['Remembrance', 'Elation'].includes(rawPath)) { - roles.specialist.push(meta.name); - } else if (['Harmony', 'Nihility'].includes(rawPath)) { - roles.support.push(meta.name); - } else if (['Preservation', 'Abundance'].includes(rawPath)) { - roles.sustain.push(meta.name); - } - // LC check const hasLc = (state.fullFreesrData?.lightcones || []).some(l => l.equip_avatar === aid); if (hasLc) lcCount++; @@ -1347,33 +1412,13 @@ function renderTeamSynergy(lineups) { const avgLevel = activeIds.length ? (totalLevel / count).toFixed(1) : '-'; const avgEidolon = activeIds.length ? `E${(totalEidolon / count).toFixed(1)}` : '-'; - // Render Element Chips - const coveredCount = Object.keys(coveredElements).length; - if (el.badgeSynergyCoverage) { - el.badgeSynergyCoverage.textContent = t('synergyCoverageBadge', { count: coveredCount }); - el.badgeSynergyCoverage.style.color = coveredCount >= 4 ? 'var(--accent-green)' : (coveredCount >= 2 ? 'var(--accent-cyan)' : 'var(--accent-gold)'); - } - - el.synergyElementsContainer.innerHTML = HSR_ELEMENTS.map(elem => { - const isCovered = !!coveredElements[elem.key]; - const providers = coveredElements[elem.key] || []; - const providerText = isCovered ? providers.join(', ') : t('synergyNoInTeam'); - return ` -
- ${getElementIconHtml(elem.key, 22)} - ${translateElement(elem.key)} - ${providerText} -
- `; - }).join(''); - // Metrics if (el.synergyAvgLevel) el.synergyAvgLevel.textContent = activeIds.length ? `Lv.${avgLevel}` : '-'; if (el.synergyAvgEidolon) el.synergyAvgEidolon.textContent = avgEidolon; if (el.synergyLcReady) el.synergyLcReady.textContent = activeIds.length ? `${lcCount}/${activeIds.length}` : '-'; if (el.synergyRelicReady) el.synergyRelicReady.textContent = activeIds.length ? `${fullRelicCount}/${activeIds.length}` : '-'; - updateDashboardBattleBanner(); + renderMiniBattleSummary(); } // ========================================== @@ -8070,23 +8115,7 @@ function renderDamageAnalyst() { } function updateDashboardBattleBanner() { - if (!el.dashboardBattleTitle || !el.dashboardBattleSummary) return; - const report = analystState.currentReport; - if (!report || !report.team_total_damage) { - el.dashboardBattleTitle.textContent = "Damage Analyst (จับดาเมจจากในเกม)"; - el.dashboardBattleSummary.textContent = "ยังไม่มีข้อมูลการต่อสู้ล่าสุด — กดเพื่อเปิดหน้า Damage Analyst"; - return; - } - const topAvatar = (report.avatars && report.avatars.length > 0) - ? [...report.avatars].sort((a, b) => b.total_damage - a.total_damage)[0] - : null; - const topName = topAvatar ? getAvatarInfo(topAvatar.avatar_id).name : ''; - const topDmgPct = topAvatar ? `${topAvatar.damage_percent.toFixed(0)}%` : ''; - - el.dashboardBattleTitle.textContent = `💥 ดาเมจรวม ${formatNumber(report.team_total_damage)} (${report.round_cnt} รอบ)`; - el.dashboardBattleSummary.textContent = topName - ? `MVP: ${topName} (${topDmgPct} Share) • ${report.total_battle_turns} เทิร์น • เวลา ${formatDuration(report.cost_time)}` - : `สถานะ: ${report.end_status || 'WIN'} • ${report.total_battle_turns} เทิร์น`; + renderMiniBattleSummary(); } function exportBattleReportJson() { @@ -8289,8 +8318,8 @@ function setupDamageAnalystEvents() { }); } - if (el.synergyDamageBanner) { - el.synergyDamageBanner.addEventListener('click', () => { + if (el.btnMiniViewFullAnalyst) { + el.btnMiniViewFullAnalyst.addEventListener('click', () => { const btn = document.querySelector('.tab-btn[data-tab="tab-damage-analyst"]'); if (btn) btn.click(); }); @@ -8305,6 +8334,7 @@ function setupDamageAnalystEvents() { } } +window.renderMiniBattleSummary = renderMiniBattleSummary; window.fetchLatestBattleReport = fetchLatestBattleReport; window.loadDemoBattleReport = loadDemoBattleReport; window.exportBattleReportJson = exportBattleReportJson; diff --git a/js/i18n.js b/js/i18n.js index d89f339..2cc5117 100644 --- a/js/i18n.js +++ b/js/i18n.js @@ -374,11 +374,23 @@ export const TRANSLATIONS = { "btnAllE0S1": "⚡ ปรับ E0S1 ทุกตัว", "qaGachabaseTitle": "ดึงข้อมูล Gachabase", "qaGachabaseDesc": "นำเข้าตัวละคร/กรวยแสง เบต้าหรือใหม่ล่าสุด", + "miniBattleTitle": "สรุปการต่อสู้ล่าสุด & ดาเมจทีม", + "miniBattleSubtitle": "สถิติดาเมจ, DPS และสัดส่วนการทำดาเมจของตัวละครในทีมจากการต่อสู้จริงล่าสุด", + "miniTotalDmg": "ดาเมจรวม", + "miniTeamDps": "DPS รวม", + "miniBreakDmg": "Break / Super Break", + "miniBattleTurns": "เทิร์น / เวลา", + "miniBreakdownTitle": "สัดส่วนดาเมจสมาชิกในทีม (Damage Share)", + "miniViewFullAnalyst": "Damage Analyst →", + "miniNoBattleTitle": "ยังไม่มีข้อมูลการต่อสู้ล่าสุด", + "miniNoBattleDesc": "เข้าสู้ในเกม (Calyx / Shadow / Boss) เพื่อเริ่มจับสถิติดาเมจแบบเรียลไทม์", + "miniBtnFetch": "🔄 ดึงข้อมูลสด", + "miniBtnDemo": "⚡ โหลดข้อมูลตัวอย่าง", "synergyTitle": "วิเคราะห์ Synergy ทีม & จุดอ่อน", "synergySubtitle": "วิเคราะห์การครอบคลุม Weakness Break, ความสมดุลของบทบาท และความพร้อมของทีม", "synergyBreakCoverage": "🎯 การทำลายจุดอ่อน (Weakness Break Coverage)", "synergyRoleBalance": "🛡️ การกระจายบทบาทในทีม (Team Composition & Roles)", - "synergyEquipmentTitle": "📊 ความพร้อมของอุปกรณ์และเลเวลเฉลี่ย", + "synergyEquipmentTitle": "🛡️ ความพร้อมของอุปกรณ์และเลเวลเฉลี่ย", "synergyAvgLevel": "เลเวลเฉลี่ย", "synergyAvgEidolon": "Eidolon เฉลี่ย", "synergyLcEquipped": "สวมกรวยแสง", @@ -807,11 +819,23 @@ export const TRANSLATIONS = { "btnGenAndEquip": "Generate & Equip", "selectFromInventory": "Select from Inventory", "btnAllE0S1": "⚡ All E0S1", + "miniBattleTitle": "Recent Battle & Team Damage", + "miniBattleSubtitle": "Live damage stats, DPS, and damage share from the latest combat", + "miniTotalDmg": "Total DMG", + "miniTeamDps": "Team DPS", + "miniBreakDmg": "Break / Super Break", + "miniBattleTurns": "Turns / Time", + "miniBreakdownTitle": "Team Damage Share", + "miniViewFullAnalyst": "Damage Analyst →", + "miniNoBattleTitle": "No Recent Battle Recorded", + "miniNoBattleDesc": "Fight in-game (Calyx / Shadow / Boss) to track real-time combat stats", + "miniBtnFetch": "🔄 Fetch Live", + "miniBtnDemo": "⚡ Load Demo Data", "synergyTitle": "Team Synergy & Weakness Analysis", "synergySubtitle": "Analyze Weakness Break coverage, role balance, and equipment readiness", "synergyBreakCoverage": "🎯 Weakness Break Coverage", "synergyRoleBalance": "🛡️ Team Composition & Role Distribution", - "synergyEquipmentTitle": "📊 Equipment Readiness & Average Level", + "synergyEquipmentTitle": "🛡️ Equipment Readiness & Average Level", "synergyAvgLevel": "Avg Level", "synergyAvgEidolon": "Avg Eidolon", "synergyLcEquipped": "Lightcones Equipped",