gesamtliste.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. <?php
  2. require_once 'config.php'; // NEU: Für IMG_URL
  3. require_once 'db_config.php';
  4. // Filter-Werte abrufen
  5. $filterPlayerId = isset($_GET['team_filter']) ? (int)$_GET['team_filter'] : 0;
  6. $filterTypId = isset($_GET['typ_filter']) ? (int)$_GET['typ_filter'] : 0;
  7. // SQL Query an neue Struktur angepasst
  8. $sql = "SELECT sp.*,
  9. t.bezeichnung as typ_name,
  10. l.bezeichnung as level_name,
  11. r.name as reihe_name,
  12. MAX(sc.sterne) as best_sterne,
  13. COUNT(sc.id) as anzahl_scores,
  14. (SELECT status FROM besitz_status WHERE spiel_id = sp.id AND spieler_id = :fid) as einzel_status,
  15. (SELECT GROUP_CONCAT(CONCAT(p.name, ': ', b.status) SEPARATOR '||')
  16. FROM besitz_status b JOIN spieler p ON b.spieler_id = p.id
  17. WHERE b.spiel_id = sp.id AND b.status != 'nicht besitzt') as besitz_info
  18. FROM spiele sp
  19. LEFT JOIN game_typ t ON sp.game_typ_id = t.id
  20. LEFT JOIN game_level l ON sp.game_level_id = l.id
  21. LEFT JOIN game_reihe r ON sp.game_reihe_id = r.id
  22. LEFT JOIN scores sc ON sp.id = sc.spiel_id " .
  23. ($filterPlayerId > 0 ? " AND sc.spieler_id = :pid " : "") . "
  24. WHERE 1=1 ";
  25. if ($filterTypId > 0) {
  26. $sql .= " AND sp.game_typ_id = :tid ";
  27. }
  28. $sql .= " GROUP BY sp.id ORDER BY r.name ASC, sp.titel ASC";
  29. $stmt = $pdo->prepare($sql);
  30. $params = ['fid' => $filterPlayerId];
  31. if ($filterPlayerId > 0) { $params['pid'] = $filterPlayerId; }
  32. if ($filterTypId > 0) { $params['tid'] = $filterTypId; }
  33. $stmt->execute($params);
  34. $inventory = $stmt->fetchAll();
  35. // Daten für die Filter-Dropdowns
  36. $spieler = $pdo->query("SELECT * FROM spieler ORDER BY name ASC")->fetchAll();
  37. $typen = $pdo->query("SELECT * FROM game_typ ORDER BY bezeichnung ASC")->fetchAll();
  38. ?>
  39. <!DOCTYPE html>
  40. <html lang="de">
  41. <head>
  42. <meta charset="UTF-8">
  43. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  44. <title>EXIT - Gesamtliste</title>
  45. <style>
  46. :root {
  47. --bg: #f8f9fa; --card: #ffffff; --text: #333; --border: #ddd;
  48. --header-bg: #2c3e50; --header-text: #ffffff; --row-border: #eee;
  49. --accent: #e67e22;
  50. }
  51. .dark-theme {
  52. --bg: #121212; --card: #1e1e1e; --text: #e0e0e0; --border: #333;
  53. --header-bg: #252525; --header-text: #e67e22; --row-border: #2a2a2a;
  54. }
  55. body { font-family: 'Segoe UI', sans-serif; background: var(--bg); margin: 0; padding: 20px; color: var(--text); transition: 0.3s; }
  56. .container { max-width: 1400px; margin: 0 auto; }
  57. .header-area { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; }
  58. .nav-group { display: flex; align-items: center; gap: 10px; }
  59. .back-link { background: var(--accent); color: white !important; text-decoration: none; font-weight: bold; padding: 10px 18px; border-radius: 6px; display: inline-block; }
  60. .filter-bar { background: var(--card); padding: 15px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.05); display: flex; gap: 15px; margin-bottom: 20px; align-items: center; border: 1px solid var(--border); flex-wrap: wrap; }
  61. .filter-bar form { display: flex; gap: 10px; flex-grow: 1; align-items: center; }
  62. select, input { padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--card); color: var(--text); }
  63. #searchInput { flex-grow: 1; min-width: 200px; }
  64. table { width: 100%; border-collapse: collapse; background: var(--card); border-radius: 12px; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.08); }
  65. th { background: var(--header-bg); color: var(--header-text); text-align: left; padding: 15px; cursor: pointer; font-size: 0.85em; }
  66. td { padding: 12px 15px; border-bottom: 1px solid var(--row-border); }
  67. .game-thumb { width: 55px; height: 55px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border); cursor: pointer; transition: 0.2s; }
  68. .game-thumb:hover { transform: scale(1.1); border-color: var(--accent); }
  69. .lvl-badge { padding: 4px 10px; border-radius: 20px; color: white; font-size: 11px; font-weight: bold; text-transform: uppercase; }
  70. .lvl-Einsteiger { background: #27ae60; }
  71. .lvl-Fortgeschrittene { background: #2980b9; }
  72. .lvl-Profi { background: #c0392b; }
  73. .lvl-unknown { background: #7f8c8d; }
  74. .st-badge { padding: 5px 12px; border-radius: 15px; font-size: 11px; font-weight: bold; }
  75. .st-besitzt { background: #d4edda; color: #155724; }
  76. .dark-theme .st-besitzt { background: #1b4332; color: #74c69d; }
  77. .st-verkauft { background: #fff3cd; color: #856404; }
  78. .st-keins { background: #f8d7da; color: #721c24; }
  79. .team-pill { display: inline-flex; align-items: center; background: var(--bg); padding: 4px 10px; border-radius: 6px; font-size: 11px; margin: 2px; border: 1px solid var(--border); }
  80. .dot { height: 8px; width: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; }
  81. #imgModal { display: none; position: fixed; z-index: 10000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); align-items: center; justify-content: center; }
  82. #modalContent { max-width: 90%; max-height: 90%; border-radius: 10px; }
  83. .image-preview { position: absolute; display: none; width: 220px; border: 4px solid white; border-radius: 12px; z-index: 999; pointer-events: none; }
  84. .theme-toggle { background: none; border: none; font-size: 1.5rem; cursor: pointer; }
  85. </style>
  86. </head>
  87. <body>
  88. <div class="container">
  89. <div class="header-area">
  90. <h1>📝 Sammlungs-Übersicht</h1>
  91. <div class="nav-group">
  92. <button onclick="toggleTheme()" class="theme-toggle" id="theme-icon">🌙</button>
  93. <a href="index.php" class="back-link">Dashboard</a>
  94. </div>
  95. </div>
  96. <div class="filter-bar">
  97. <form method="GET" id="filterForm">
  98. <select name="team_filter" onchange="this.form.submit()">
  99. <option value="0">-- Alle Teams --</option>
  100. <?php foreach ($spieler as $p): ?>
  101. <option value="<?= $p['id'] ?>" <?= $filterPlayerId == $p['id'] ? 'selected' : '' ?>><?= htmlspecialchars($p['name']) ?></option>
  102. <?php endforeach; ?>
  103. </select>
  104. <select name="typ_filter" onchange="this.form.submit()">
  105. <option value="0">-- Alle Typen --</option>
  106. <?php foreach ($typen as $t): ?>
  107. <option value="<?= $t['id'] ?>" <?= $filterTypId == $t['id'] ? 'selected' : '' ?>><?= htmlspecialchars($t['bezeichnung']) ?></option>
  108. <?php endforeach; ?>
  109. </select>
  110. </form>
  111. <input type="text" id="searchInput" placeholder="Titel oder Reihe suchen..." onkeyup="filterTable()">
  112. </div>
  113. <table id="exitTable">
  114. <thead>
  115. <tr>
  116. <th>Bild</th>
  117. <th onclick="sortTable(1)">Spiel / Reihe ↕</th>
  118. <th onclick="sortTable(2)">Level ↕</th>
  119. <th onclick="sortTable(3)">Bestwert ↕</th>
  120. <th>Besitzstatus</th>
  121. </tr>
  122. </thead>
  123. <tbody>
  124. <?php foreach ($inventory as $row):
  125. $lvlName = $row['level_name'] ?: 'unknown';
  126. $typ = $row['typ_name'] ?: 'Standard';
  127. $reihe = $row['reihe_name'] ?: 'Sonstige';
  128. // MINIMALE ÄNDERUNG: Pfad-Logik für Bilder
  129. $b = $row['bild_url'];
  130. $imgSrc = empty($b) ? 'https://via.placeholder.com/60?text=?' : (strpos($b, 'http') === 0 ? $b : IMG_URL . $b);
  131. ?>
  132. <tr>
  133. <td>
  134. <img src="<?= htmlspecialchars($imgSrc) ?>"
  135. class="game-thumb"
  136. onclick="openModal('<?= htmlspecialchars($imgSrc) ?>')"
  137. onmouseover="showPreview(event, '<?= htmlspecialchars($imgSrc) ?>')"
  138. onmouseout="hidePreview()"
  139. onerror="this.src='https://via.placeholder.com/60?text=?'">
  140. </td>
  141. <td>
  142. <strong><?= htmlspecialchars($row['titel']) ?></strong><br>
  143. <small style="opacity: 0.7;"><?= htmlspecialchars($reihe) ?> (<?= htmlspecialchars($typ) ?>)</small>
  144. </td>
  145. <td>
  146. <span class="lvl-badge lvl-<?= htmlspecialchars($lvlName) ?>">
  147. <?= htmlspecialchars($lvlName == 'unknown' ? 'Unbekannt' : $lvlName) ?>
  148. </span>
  149. </td>
  150. <td>
  151. <?php
  152. if ($row['best_sterne'] > 0) {
  153. echo "<b>{$row['best_sterne']} ★</b>";
  154. } elseif ($row['anzahl_scores'] > 0) {
  155. echo "<i style='opacity:0.7'>Gespielt</i>";
  156. } else {
  157. echo "-";
  158. }
  159. ?>
  160. </td>
  161. <td>
  162. <?php if ($filterPlayerId > 0):
  163. $st = $row['einzel_status'] ?: 'nicht besitzt';
  164. $class = ($st == 'besitzt') ? 'st-besitzt' : (($st == 'verkauft') ? 'st-verkauft' : 'st-keins');
  165. echo "<span class='st-badge $class'>".strtoupper($st)."</span>";
  166. else:
  167. if ($row['besitz_info']) {
  168. foreach (explode('||', $row['besitz_info']) as $t) {
  169. $parts = explode(': ', $t);
  170. if(count($parts) < 2) continue;
  171. list($tn, $ts) = $parts;
  172. $dot = ($ts == 'besitzt') ? '#27ae60' : '#f39c12';
  173. echo "<span class='team-pill'><span class='dot' style='background:$dot'></span>$tn</span>";
  174. }
  175. } else { echo "<small>-</small>"; }
  176. endif; ?>
  177. </td>
  178. </tr>
  179. <?php endforeach; ?>
  180. </tbody>
  181. </table>
  182. </div>
  183. <div id="imgModal" onclick="this.style.display='none'"><img id="modalContent"></div>
  184. <img id="hoverPreview" class="image-preview" src="">
  185. <script>
  186. function toggleTheme() {
  187. const isDark = document.documentElement.classList.toggle('dark-theme');
  188. localStorage.setItem('theme', isDark ? 'dark' : 'light');
  189. document.getElementById('theme-icon').innerText = isDark ? '☀️' : '🌙';
  190. }
  191. if (localStorage.getItem('theme') === 'dark') {
  192. document.documentElement.classList.add('dark-theme');
  193. document.getElementById('theme-icon').innerText = '☀️';
  194. }
  195. const modal = document.getElementById("imgModal");
  196. const modalImg = document.getElementById("modalContent");
  197. function openModal(url) { modal.style.display = "flex"; modalImg.src = url; hidePreview(); }
  198. const preview = document.getElementById('hoverPreview');
  199. function showPreview(e, url) {
  200. if(modal.style.display === "flex") return;
  201. preview.src = url; preview.style.display = 'block'; updatePos(e);
  202. }
  203. function hidePreview() { preview.style.display = 'none'; }
  204. function updatePos(e) {
  205. preview.style.left = (e.pageX + 20) + 'px';
  206. preview.style.top = (e.pageY - 100) + 'px';
  207. }
  208. document.addEventListener('mousemove', (e) => { if(preview.style.display==='block') updatePos(e); });
  209. function filterTable() {
  210. let val = document.getElementById("searchInput").value.toUpperCase();
  211. let tr = document.getElementById("exitTable").getElementsByTagName("tr");
  212. for (let i = 1; i < tr.length; i++) tr[i].style.display = tr[i].innerText.toUpperCase().includes(val) ? "" : "none";
  213. }
  214. function sortTable(n) {
  215. var table = document.getElementById("exitTable");
  216. var rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
  217. switching = true; dir = "asc";
  218. while (switching) {
  219. switching = false; rows = table.rows;
  220. for (i = 1; i < (rows.length - 1); i++) {
  221. shouldSwitch = false;
  222. x = rows[i].getElementsByTagName("TD")[n];
  223. y = rows[i + 1].getElementsByTagName("TD")[n];
  224. let valX = x.innerText.toLowerCase().trim();
  225. let valY = y.innerText.toLowerCase().trim();
  226. if (dir == "asc") { if (valX > valY) { shouldSwitch = true; break; } }
  227. else if (dir == "desc") { if (valX < valY) { shouldSwitch = true; break; } }
  228. }
  229. if (shouldSwitch) {
  230. rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
  231. switching = true; switchcount ++;
  232. } else {
  233. if (switchcount == 0 && dir == "asc") { dir = "desc"; switching = true; }
  234. }
  235. }
  236. }
  237. </script>
  238. </body>
  239. </html>