Skip to content

Commit 4cd7419

Browse files
authored
Add files via upload
1 parent 3ac3b13 commit 4cd7419

2 files changed

Lines changed: 314 additions & 3 deletions

File tree

index.html

Lines changed: 307 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ <h3>Gildenrat-Login</h3>
307307
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
308308
import { getFirestore, doc, setDoc, onSnapshot, collection, deleteDoc, getDoc, serverTimestamp, query, orderBy, addDoc, updateDoc, where, getDocs } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
309309
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithEmailAndPassword, signOut } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
310-
310+
311311
// ============== INITIALISIERUNG & GLOBALE VARIABLEN ==============
312312
const firebaseConfig = {
313313
apiKey: "AIzaSyBmqCCIOKq0OQOTEgJJ7Lj8CYlLihVBVSU",
@@ -329,6 +329,9 @@ <h3>Gildenrat-Login</h3>
329329
let lootDatesUnsubscribe = null;
330330
let selectedLootDateUnsubscribe = null;
331331
let heartbeatIntervalId = null;
332+
let allLootDocuments = [];
333+
let playerSummaryState = {};
334+
let summarySortState = { column: 'total', direction: 'desc' };
332335

333336
const DATA_COLLECTION = "raid-tool-data";
334337
const HISTORY_COLLECTION = "raid-tool-history";
@@ -734,7 +737,6 @@ <h3>Nachricht</h3>
734737
});
735738
}
736739

737-
// --- Logik für loot.html ---
738740
function initLootPage() {
739741
const importSection = document.getElementById('loot-import-section');
740742
const importBtn = document.getElementById('import-loot-btn');
@@ -744,6 +746,15 @@ <h3>Nachricht</h3>
744746
importBtn?.addEventListener('click', handleLootImport);
745747
}
746748

749+
// NEU: Event listener für den Zusammenfassungs-Button
750+
document.getElementById('show-player-summary-btn')?.addEventListener('click', showPlayerSummaryView);
751+
752+
// Alle Loot-Daten einmalig beim Laden der Seite abrufen
753+
const allLootQuery = query(lootCollectionRef, orderBy("raidDate", "desc"));
754+
getDocs(allLootQuery).then(snapshot => {
755+
allLootDocuments = snapshot.docs.map(doc => ({ id: doc.id, data: doc.data().lootData }));
756+
});
757+
747758
const q = query(lootCollectionRef, orderBy("raidDate", "desc"));
748759
lootDatesUnsubscribe = onSnapshot(q, (snapshot) => {
749760
const datesList = document.getElementById('loot-dates-list');
@@ -764,6 +775,7 @@ <h3>Nachricht</h3>
764775
dateButton.onclick = () => {
765776
document.querySelectorAll('#loot-dates-list button').forEach(btn => btn.classList.remove('active-tab'));
766777
dateButton.classList.add('active-tab');
778+
// Diese Funktion schaltet automatisch zurück zur Einzelansicht
767779
displayLootForDate(date);
768780
};
769781
datesList.appendChild(dateButton);
@@ -805,6 +817,8 @@ <h3>Nachricht</h3>
805817
if (selectedLootDateUnsubscribe) {
806818
selectedLootDateUnsubscribe();
807819
}
820+
const viewTitle = document.getElementById('loot-view-title');
821+
if(viewTitle) viewTitle.textContent = "Loot-Details";
808822

809823
const lootDocRef = doc(db, LOOT_COLLECTION, dateId);
810824
const displayContainer = document.getElementById('loot-details-display');
@@ -1239,6 +1253,297 @@ <h4 class="text-lg font-bold" style="color: var(--color-gold);">${itemLinkHtml}<
12391253
};
12401254
});
12411255
};
1256+
function showPlayerSummaryView() {
1257+
const displayContainer = document.getElementById('loot-details-display');
1258+
const viewTitle = document.getElementById('loot-view-title');
1259+
if (!displayContainer || !viewTitle) return;
1260+
1261+
viewTitle.textContent = "Spieler-Zusammenfassung";
1262+
playerSummaryState = {}; // Zustand zurücksetzen
1263+
summarySortState = { column: 'total', direction: 'desc' };
1264+
1265+
1266+
if (allLootDocuments.length === 0) {
1267+
displayContainer.innerHTML = '<p class="text-gray-500">Keine Loot-Daten zum Auswerten vorhanden.</p>';
1268+
return;
1269+
}
1270+
1271+
const datesHtml = allLootDocuments.map(doc => `
1272+
<label class="flex items-center space-x-2 p-2 rounded-md hover:bg-slate-700 cursor-pointer transition-colors duration-150">
1273+
<input type="checkbox" data-date-id="${doc.id}" class="summary-date-checkbox form-checkbox h-5 w-5 rounded bg-slate-900 border-slate-600 text-jade-400 focus:ring-jade-400">
1274+
<span>${new Date(doc.id + 'T12:00:00Z').toLocaleDateString('de-DE', { month: 'long', day: 'numeric' })}</span>
1275+
</label>
1276+
`).join('');
1277+
1278+
displayContainer.innerHTML = `
1279+
<div class="flex justify-between items-center mb-4">
1280+
<p class="text-gray-400">Wähle die Daten aus, die einbezogen werden sollen.</p>
1281+
<div class="flex gap-2">
1282+
<button id="summary-select-all-btn" class="text-sm py-1 px-3 rounded-md bg-blue-600 hover:bg-blue-700">Alle</button>
1283+
<button id="summary-deselect-all-btn" class="text-sm py-1 px-3 rounded-md bg-slate-600 hover:bg-slate-500">Keine</button>
1284+
</div>
1285+
</div>
1286+
<div id="summary-dates-grid" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 mb-6 p-4 bg-slate-900/50 rounded-lg border border-slate-700">
1287+
${datesHtml}
1288+
</div>
1289+
<div id="player-summary-results">
1290+
<p class="text-gray-500 text-center py-4">Bitte mindestens ein Datum auswählen.</p>
1291+
</div>
1292+
`;
1293+
1294+
// KORREKTUR: Der Listener wird präziser gesetzt, um das Bubbling-Problem zu beheben.
1295+
document.getElementById('summary-dates-grid').addEventListener('change', (event) => {
1296+
if (event.target.classList.contains('summary-date-checkbox')) {
1297+
handleSummaryDateSelection();
1298+
}
1299+
});
1300+
document.getElementById('summary-select-all-btn')?.addEventListener('click', () => toggleAllSummaryCheckboxes(true));
1301+
document.getElementById('summary-deselect-all-btn')?.addEventListener('click', () => toggleAllSummaryCheckboxes(false));
1302+
}
1303+
1304+
function toggleAllSummaryCheckboxes(select) {
1305+
document.querySelectorAll('.summary-date-checkbox').forEach(cb => cb.checked = select);
1306+
handleSummaryDateSelection();
1307+
}
1308+
1309+
function handleSummaryDateSelection() {
1310+
const selectedDates = Array.from(document.querySelectorAll('.summary-date-checkbox:checked'))
1311+
.map(cb => cb.dataset.dateId);
1312+
1313+
const oldState = { ...playerSummaryState }; // Den alten Zustand für die offenen Menüs sichern
1314+
playerSummaryState = {};
1315+
1316+
if (selectedDates.length > 0) {
1317+
const filteredLootItems = allLootDocuments
1318+
.filter(doc => selectedDates.includes(doc.id))
1319+
.flatMap(doc => doc.data);
1320+
1321+
filteredLootItems.forEach(item => {
1322+
if (!item.received || !item.checksum) return; // Sicherstellen, dass ein Checksum vorhanden ist
1323+
1324+
const winner = item.awardedTo.split('-')[0];
1325+
let rollTypeSource = '';
1326+
1327+
if (item.winningRollType) {
1328+
rollTypeSource = item.winningRollType;
1329+
} else {
1330+
const winningRoll = item.Rolls.find(roll => roll.player === winner);
1331+
if (winningRoll && winningRoll.classification) {
1332+
rollTypeSource = winningRoll.classification;
1333+
} else { return; }
1334+
}
1335+
1336+
const rollType = rollTypeSource.toUpperCase();
1337+
let category;
1338+
if (rollType === 'MS') category = 'ms';
1339+
else if (rollType === 'OS') category = 'os';
1340+
else if (['T-MOG', 'TRANSMOG', 'STYLE'].includes(rollType)) category = 'transmog';
1341+
else return;
1342+
1343+
if (!playerSummaryState[winner]) {
1344+
playerSummaryState[winner] = { items: [], isDetailsOpen: oldState[winner]?.isDetailsOpen || false };
1345+
}
1346+
1347+
playerSummaryState[winner].items.push({
1348+
name: item.itemLink.replace(/[\[\]]/g, ''),
1349+
id: item.itemID,
1350+
// KORREKTUR: Wir verwenden item.checksum statt item.itemGUID
1351+
uniqueId: item.checksum,
1352+
category: category,
1353+
included: true
1354+
});
1355+
});
1356+
}
1357+
1358+
drawPlayerSummaryTable();
1359+
}
1360+
1361+
function renderPlayerSummary(selectedDates) {
1362+
const resultsContainer = document.getElementById('player-summary-results');
1363+
if (selectedDates.length === 0) {
1364+
resultsContainer.innerHTML = '<p class="text-gray-500 text-center py-4">Bitte mindestens ein Datum auswählen.</p>';
1365+
return;
1366+
}
1367+
1368+
const playerStats = {};
1369+
const filteredLootItems = allLootDocuments
1370+
.filter(doc => selectedDates.includes(doc.id))
1371+
.flatMap(doc => doc.data);
1372+
1373+
filteredLootItems.forEach(item => {
1374+
if (!item.received) return;
1375+
1376+
const winner = item.awardedTo.split('-')[0];
1377+
let rollTypeSource = '';
1378+
1379+
// --- NEUE, INTELLIGENTERE LOGIK ---
1380+
// 1. Versuche, den direkten Typ zu verwenden
1381+
if (item.winningRollType) {
1382+
rollTypeSource = item.winningRollType;
1383+
}
1384+
// 2. Wenn das Feld fehlt, finde den Wurf des Gewinners und nutze dessen Klassifizierung
1385+
else {
1386+
const winningRoll = item.Rolls.find(roll => roll.player === winner);
1387+
if (winningRoll && winningRoll.classification) {
1388+
rollTypeSource = winningRoll.classification;
1389+
} else {
1390+
// Wenn wir den Typ nicht bestimmen können, überspringe das Item.
1391+
return;
1392+
}
1393+
}
1394+
// --- ENDE DER NEUEN LOGIK ---
1395+
1396+
const rollType = rollTypeSource.toUpperCase();
1397+
let category;
1398+
if (rollType === 'MS') {
1399+
category = 'ms';
1400+
} else if (rollType === 'OS') {
1401+
category = 'os';
1402+
} else if (['T-MOG', 'TRANSMOG', 'STYLE'].includes(rollType)) {
1403+
category = 'transmog';
1404+
} else {
1405+
return;
1406+
}
1407+
1408+
if (!playerStats[winner]) {
1409+
playerStats[winner] = { ms: 0, os: 0, transmog: 0, total: 0 };
1410+
}
1411+
1412+
playerStats[winner][category]++;
1413+
playerStats[winner].total++;
1414+
});
1415+
1416+
const statsArray = Object.entries(playerStats).map(([name, stats]) => ({ name, ...stats }));
1417+
statsArray.sort((a, b) => b.total - a.total || a.name.localeCompare(b.name));
1418+
1419+
const tableRows = statsArray.map(player => `
1420+
<tr class="border-b border-slate-700 hover:bg-slate-750/50">
1421+
<td class="px-4 py-3 font-medium">${player.name}</td>
1422+
<td class="px-4 py-3 text-center">${player.ms}</td>
1423+
<td class="px-4 py-3 text-center">${player.os}</td>
1424+
<td class="px-4 py-3 text-center">${player.transmog}</td>
1425+
<td class="px-4 py-3 text-center font-bold text-gold">${player.total}</td>
1426+
</tr>
1427+
`).join('');
1428+
1429+
resultsContainer.innerHTML = `
1430+
<div class="overflow-x-auto rounded-lg border border-slate-700">
1431+
<table class="min-w-full text-sm text-left text-gray-300">
1432+
<thead class="text-xs text-gray-400 uppercase bg-slate-700">
1433+
<tr>
1434+
<th scope="col" class="px-4 py-3">Spieler</th>
1435+
<th scope="col" class="px-4 py-3 text-center">MS</th>
1436+
<th scope="col" class="px-4 py-3 text-center">OS</th>
1437+
<th scope="col" class="px-4 py-3 text-center">Transmog</th>
1438+
<th scope="col" class="px-4 py-3 text-center">Total</th>
1439+
</tr>
1440+
</thead>
1441+
<tbody class="divide-y divide-slate-700">
1442+
${tableRows.length > 0 ? tableRows : '<tr><td colspan="5" class="text-center p-4 text-gray-500">Keine zählbaren Loot-Daten für die Auswahl gefunden.</td></tr>'}
1443+
</tbody>
1444+
</table>
1445+
</div>
1446+
`;
1447+
}
1448+
function drawPlayerSummaryTable() {
1449+
const resultsContainer = document.getElementById('player-summary-results');
1450+
const players = Object.keys(playerSummaryState);
1451+
1452+
if (players.length === 0) {
1453+
resultsContainer.innerHTML = '<p class="text-gray-500 text-center py-4">Bitte mindestens ein Datum auswählen.</p>';
1454+
return;
1455+
}
1456+
1457+
const statsArray = players.map(name => {
1458+
const includedItems = playerSummaryState[name].items.filter(item => item.included);
1459+
return {
1460+
name: name,
1461+
ms: includedItems.filter(item => item.category === 'ms').length,
1462+
os: includedItems.filter(item => item.category === 'os').length,
1463+
transmog: includedItems.filter(item => item.category === 'transmog').length,
1464+
total: includedItems.length
1465+
};
1466+
});
1467+
1468+
// --- NEU: Sortierlogik ---
1469+
const { column, direction } = summarySortState;
1470+
statsArray.sort((a, b) => {
1471+
if (a[column] !== b[column]) {
1472+
return direction === 'desc' ? b[column] - a[column] : a[column] - b[column];
1473+
}
1474+
// Sekundäre Sortierung nach Total, dann nach Name
1475+
if (b.total !== a.total) return b.total - a.total;
1476+
return a.name.localeCompare(b.name);
1477+
});
1478+
// --- ENDE Sortierlogik ---
1479+
1480+
const tableRows = statsArray.map(player => {
1481+
const itemsHtml = playerSummaryState[player.name].items.map(item => {
1482+
const wowheadUrl = `https://www.wowhead.com/mop-classic/item=${item.id}`;
1483+
return `<div class="flex items-center gap-2 px-2 py-1"><input type="checkbox" class="item-inclusion-checkbox" data-player-name="${player.name}" data-item-id="${item.uniqueId}" ${item.included ? 'checked' : ''}><a href="${wowheadUrl}" target="_blank" rel="noopener noreferrer" class="hover:underline text-sm">[${item.name}]</a><span class="text-xs uppercase bg-slate-600 px-1.5 py-0.5 rounded-full">${item.category}</span></div>`;
1484+
}).join('');
1485+
const isHidden = playerSummaryState[player.name].isDetailsOpen ? '' : 'hidden';
1486+
const rotation = playerSummaryState[player.name].isDetailsOpen ? 'rotate-90' : '';
1487+
return `<tr class="border-b border-slate-700 hover:bg-slate-750/50"><td class="px-4 py-3 font-medium"><button class="toggle-details-btn flex items-center gap-2" data-player-name="${player.name}">${player.name} <span class="text-xs text-gray-400 transition-transform ${rotation}">▶</span></button></td><td class="px-4 py-3 text-center">${player.ms}</td><td class="px-4 py-3 text-center">${player.os}</td><td class="px-4 py-3 text-center">${player.transmog}</td><td class="px-4 py-3 text-center font-bold text-gold">${player.total}</td></tr><tr class="item-details-row ${isHidden} bg-slate-800"><td colspan="5" class="p-2"><div class="flex flex-col gap-1">${itemsHtml}</div></td></tr>`;
1488+
}).join('');
1489+
1490+
// --- NEU: Dynamische Kopfzeile mit Sortier-Buttons ---
1491+
const sortableColumns = { 'MS': 'ms', 'OS': 'os', 'Transmog': 'transmog', 'Total': 'total' };
1492+
const headerHtml = Object.entries(sortableColumns).map(([title, key]) => {
1493+
const isActive = summarySortState.column === key;
1494+
const indicator = isActive ? (summarySortState.direction === 'desc' ? '▼' : '▲') : '';
1495+
return `<th scope="col" class="px-4 py-3 text-center"><button class="sort-btn font-bold uppercase ${isActive ? 'text-gold' : 'text-gray-400'}" data-sort-column="${key}">${title} <span class="indicator w-4 inline-block">${indicator}</span></button></th>`;
1496+
}).join('');
1497+
1498+
resultsContainer.innerHTML = `<div class="overflow-x-auto rounded-lg border border-slate-700"><table class="min-w-full text-sm text-left text-gray-300"><thead class="text-xs uppercase bg-slate-700"><tr><th scope="col" class="px-4 py-3">Spieler</th>${headerHtml}</tr></thead><tbody class="divide-y divide-slate-700">${tableRows}</tbody></table></div>`;
1499+
1500+
// Event Listener hinzufügen
1501+
resultsContainer.querySelectorAll('.toggle-details-btn').forEach(btn => btn.addEventListener('click', handleToggleDetails));
1502+
resultsContainer.querySelectorAll('.item-inclusion-checkbox').forEach(cb => cb.addEventListener('change', handleItemInclusionChange));
1503+
resultsContainer.querySelectorAll('.sort-btn').forEach(btn => btn.addEventListener('click', handleSummarySort));
1504+
}
1505+
function handleSummarySort(event) {
1506+
const newColumn = event.currentTarget.dataset.sortColumn;
1507+
if (summarySortState.column === newColumn) {
1508+
// Wenn die selbe Spalte geklickt wird, Richtung umkehren
1509+
summarySortState.direction = summarySortState.direction === 'desc' ? 'asc' : 'desc';
1510+
} else {
1511+
// Bei Klick auf eine neue Spalte, diese als aktiv setzen und Standardrichtung (desc) wählen
1512+
summarySortState.column = newColumn;
1513+
summarySortState.direction = 'desc';
1514+
}
1515+
// Tabelle mit neuer Sortierung neu zeichnen
1516+
drawPlayerSummaryTable();
1517+
}
1518+
1519+
// ZWEI WEITERE NEUE FUNKTIONEN
1520+
function handleToggleDetails(event) {
1521+
const button = event.currentTarget;
1522+
// KORREKTUR: Spielername wird aus data-Attribut gelesen (sicherer)
1523+
const playerName = button.dataset.playerName;
1524+
1525+
if (playerSummaryState[playerName]) {
1526+
// Den Zustand im State-Objekt umschalten
1527+
playerSummaryState[playerName].isDetailsOpen = !playerSummaryState[playerName].isDetailsOpen;
1528+
}
1529+
1530+
// Tabelle neu zeichnen, um die Änderung (offen/geschlossen) anzuzeigen
1531+
drawPlayerSummaryTable();
1532+
}
1533+
1534+
function handleItemInclusionChange(event) {
1535+
const checkbox = event.target;
1536+
const playerName = checkbox.dataset.playerName;
1537+
// KORREKTUR: Wir lesen das data-item-id Attribut
1538+
const itemId = checkbox.dataset.itemId;
1539+
1540+
// KORREKTUR: Wir finden das Item anhand der neuen uniqueId Eigenschaft
1541+
const item = playerSummaryState[playerName]?.items.find(i => i.uniqueId === itemId);
1542+
if (item) {
1543+
item.included = checkbox.checked;
1544+
}
1545+
drawPlayerSummaryTable();
1546+
}
12421547
</script>
12431548
</body>
12441549
</html>

0 commit comments

Comments
 (0)