9494 border : 1px solid var (--color-gold-border ) !important ;
9595 color : var (--color-parchment ) !important ;
9696 }
97- # json-input { background-color : rgba (10 , 20 , 15 , 0.7 ) !important ; border : 1px solid var (--color-gold-border ) !important ; color : # a0aec0 !important ; }
97+ # json-input , # loot-json-input { background-color : rgba (10 , 20 , 15 , 0.7 ) !important ; border : 1px solid var (--color-gold-border ) !important ; color : # a0aec0 !important ; }
9898
9999 # background-video { position : fixed; right : 0 ; bottom : 0 ; min-width : 100% ; min-height : 100% ; z-index : -100 ; object-fit : cover; filter : brightness (0.4 ); }
100100 .content-wrapper { position : relative; z-index : 2 ; }
@@ -326,11 +326,14 @@ <h3>Gildenrat-Login</h3>
326326 let currentRosterUnsubscribe = null ;
327327 let historyUnsubscribe = null ;
328328 let assignmentUnsubscribe = null ;
329+ let lootDatesUnsubscribe = null ;
330+ let selectedLootDateUnsubscribe = null ;
329331 let heartbeatIntervalId = null ;
330332
331333 const DATA_COLLECTION = "raid-tool-data" ;
332334 const HISTORY_COLLECTION = "raid-tool-history" ;
333335 const USER_PROFILES_COLLECTION = "user_profiles" ;
336+ const LOOT_COLLECTION = "raid-tool-loot" ;
334337
335338 const raidData = {
336339 mogushan : {
@@ -408,6 +411,7 @@ <h3>Gildenrat-Login</h3>
408411 const rosterDocRef = doc ( db , DATA_COLLECTION , "currentRoster" ) ;
409412 const historyCollectionRef = collection ( db , HISTORY_COLLECTION ) ;
410413 const userProfilesCollectionRef = collection ( db , USER_PROFILES_COLLECTION ) ;
414+ const lootCollectionRef = collection ( db , LOOT_COLLECTION ) ;
411415
412416 // ============== MODAL-FUNKTION (global verfügbar) ==============
413417 window . showModal = function ( message , isConfirm = false ) {
@@ -599,7 +603,8 @@ <h3>Nachricht</h3>
599603 const pageId = `${ raidId } /${ boss . id } ` ;
600604 navHTML += `<a href="#${ pageId } " data-page-id="${ pageId } " class="nav-link hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-md transition-colors duration-200">${ boss . name } </a>` ;
601605 } ) ;
602-
606+
607+ navHTML += `<a href="#loot" data-page-id="loot" class="nav-link text-white font-bold py-2 px-4 rounded-md transition-colors duration-200" style="background-color: #7c3aed; border-color: transparent;">Loot</a>` ;
603608 navHTML += `<a href="#history" data-page-id="history" class="nav-link bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-2 px-4 rounded-md transition-colors duration-200">Änderungsverlauf</a>` ;
604609 bossNav . innerHTML = navHTML ;
605610 }
@@ -608,6 +613,8 @@ <h3>Nachricht</h3>
608613 if ( currentRosterUnsubscribe ) { currentRosterUnsubscribe ( ) ; currentRosterUnsubscribe = null ; }
609614 if ( historyUnsubscribe ) { historyUnsubscribe ( ) ; historyUnsubscribe = null ; }
610615 if ( assignmentUnsubscribe ) { assignmentUnsubscribe ( ) ; assignmentUnsubscribe = null ; }
616+ if ( lootDatesUnsubscribe ) { lootDatesUnsubscribe ( ) ; lootDatesUnsubscribe = null ; }
617+ if ( selectedLootDateUnsubscribe ) { selectedLootDateUnsubscribe ( ) ; selectedLootDateUnsubscribe = null ; }
611618
612619 const filePath = pageId . includes ( '/' ) ? `${ pageId } .html` : `${ pageId } .html` ;
613620
@@ -648,11 +655,6 @@ <h3>Nachricht</h3>
648655 setupAuthUI ( ) ;
649656
650657 raidSelector . addEventListener ( 'change' , ( ) => {
651- // ++ KORRIGIERTE LOGIK ++
652- // Wenn der User den Raid wechselt, soll zur #comp Seite navigiert werden.
653- // Wenn wir schon auf #comp sind, würde das `hashchange` Event nicht feuern.
654- // Deshalb rufen wir in diesem Fall die Render-Funktion manuell auf.
655- // Andernfalls setzen wir den Hash, was das Event automatisch auslöst.
656658 if ( window . location . hash === '#comp' || window . location . hash === '' ) {
657659 renderCurrentState ( ) ;
658660 } else {
@@ -662,7 +664,6 @@ <h3>Nachricht</h3>
662664
663665 window . addEventListener ( 'hashchange' , renderCurrentState ) ;
664666
665- // Initialer Seitenaufbau
666667 renderCurrentState ( ) ;
667668 } ) ;
668669
@@ -683,9 +684,10 @@ <h3>Nachricht</h3>
683684 initCompPage ( ) ;
684685 } else if ( pageId === 'history' ) {
685686 initHistoryPage ( ) ;
687+ } else if ( pageId === 'loot' ) {
688+ initLootPage ( ) ;
686689 } else if ( pageId === 'impressum' || pageId === 'datenschutz' ) {
687- // Für diese einfachen Text-Seiten ist keine weitere Javascript-Aktion nötig.
688- // Der Inhalt wurde bereits geladen, wir sind hier fertig.
690+ // No JS needed
689691 } else {
690692 initBossPage ( pageId ) ;
691693 }
@@ -731,6 +733,144 @@ <h3>Nachricht</h3>
731733 } ) ;
732734 } ) ;
733735 }
736+
737+ // --- Logik für loot.html ---
738+ function initLootPage ( ) {
739+ const importSection = document . getElementById ( 'loot-import-section' ) ;
740+ const importBtn = document . getElementById ( 'import-loot-btn' ) ;
741+
742+ if ( window . isManager ) {
743+ importSection . style . display = 'block' ;
744+ importBtn ?. addEventListener ( 'click' , handleLootImport ) ;
745+ }
746+
747+ const q = query ( lootCollectionRef , orderBy ( "raidDate" , "desc" ) ) ;
748+ lootDatesUnsubscribe = onSnapshot ( q , ( snapshot ) => {
749+ const datesList = document . getElementById ( 'loot-dates-list' ) ;
750+ if ( ! datesList ) return ;
751+
752+ if ( snapshot . empty ) {
753+ datesList . innerHTML = '<p class="text-gray-500">Keine Loot-Daten gefunden.</p>' ;
754+ return ;
755+ }
756+
757+ datesList . innerHTML = '' ;
758+ snapshot . forEach ( doc => {
759+ const date = doc . id ;
760+ const dateButton = document . createElement ( 'button' ) ;
761+ dateButton . className = 'w-full text-left p-2 rounded-md nav-link' ;
762+ dateButton . textContent = new Date ( date + 'T12:00:00Z' ) . toLocaleDateString ( 'de-DE' , { year : 'numeric' , month : 'long' , day : 'numeric' } ) ;
763+ dateButton . dataset . dateId = date ;
764+ dateButton . onclick = ( ) => {
765+ document . querySelectorAll ( '#loot-dates-list button' ) . forEach ( btn => btn . classList . remove ( 'active-tab' ) ) ;
766+ dateButton . classList . add ( 'active-tab' ) ;
767+ displayLootForDate ( date ) ;
768+ } ;
769+ datesList . appendChild ( dateButton ) ;
770+ } ) ;
771+ } ) ;
772+ }
773+
774+ async function handleLootImport ( ) {
775+ if ( ! window . isManager ) return ;
776+ const jsonString = document . getElementById ( 'loot-json-input' ) . value ;
777+ if ( ! jsonString ) return window . showModal ( "Bitte JSON einfügen." ) ;
778+
779+ try {
780+ const data = JSON . parse ( jsonString ) ;
781+ if ( ! Array . isArray ( data ) || data . length === 0 ) {
782+ throw new Error ( "JSON muss ein Array von Loot-Objekten sein und darf nicht leer sein." ) ;
783+ }
784+
785+ const firstTimestamp = data [ 0 ] . timestamp ;
786+ if ( ! firstTimestamp ) throw new Error ( "Erstes Objekt im JSON hat keinen 'timestamp'." ) ;
787+
788+ const raidDate = new Date ( firstTimestamp * 1000 ) . toISOString ( ) . split ( 'T' ) [ 0 ] ; // Format: YYYY-MM-DD
789+
790+ const lootDocRef = doc ( db , LOOT_COLLECTION , raidDate ) ;
791+ await setDoc ( lootDocRef , { lootData : data , raidDate : raidDate } ) ;
792+
793+ const currentManager = sessionStorage . getItem ( 'currentManager' ) || 'Unbekannt' ;
794+ window . logHistory ( 'Loot' , `Importiert für ${ raidDate } ` , `${ data . length } Items` , currentManager ) ;
795+ window . showModal ( `Loot für den ${ new Date ( raidDate + 'T12:00:00Z' ) . toLocaleDateString ( 'de-DE' ) } wurde erfolgreich importiert!` ) ;
796+ document . getElementById ( 'loot-json-input' ) . value = '' ;
797+
798+ } catch ( error ) {
799+ window . showModal ( "Fehler beim Verarbeiten des JSON: " + error . message ) ;
800+ console . error ( error ) ;
801+ }
802+ }
803+
804+ function displayLootForDate ( dateId ) {
805+ if ( selectedLootDateUnsubscribe ) {
806+ selectedLootDateUnsubscribe ( ) ;
807+ }
808+
809+ const lootDocRef = doc ( db , LOOT_COLLECTION , dateId ) ;
810+ const displayContainer = document . getElementById ( 'loot-details-display' ) ;
811+ displayContainer . innerHTML = '<p class="text-gray-400">Lade Loot-Daten...</p>' ;
812+
813+ selectedLootDateUnsubscribe = onSnapshot ( lootDocRef , ( docSnap ) => {
814+ if ( ! docSnap . exists ( ) ) {
815+ displayContainer . innerHTML = '<p class="text-red-400">Fehler: Loot-Daten für dieses Datum nicht gefunden.</p>' ;
816+ return ;
817+ }
818+
819+ const lootData = docSnap . data ( ) . lootData ;
820+ lootData . sort ( ( a , b ) => a . timestamp - b . timestamp ) ;
821+
822+ let html = '' ;
823+ lootData . forEach ( item => {
824+ const awardedTo = item . awardedTo . split ( '-' ) [ 0 ] ;
825+ const winnerRoll = item . Rolls . find ( r => r . player === awardedTo ) ;
826+ const winnerClass = winnerRoll ? winnerRoll . class . toUpperCase ( ) : 'UNKNOWN' ;
827+ const winnerColor = window . classColors [ winnerClass ] || '#FFFFFF' ;
828+
829+ const rollsHtml = item . Rolls
830+ . sort ( ( a , b ) => b . amount - a . amount )
831+ . map ( roll => {
832+ const rollType = roll . classification ;
833+ let maxRoll = 100 ;
834+ if ( rollType === 'OS' ) maxRoll = 50 ;
835+ else if ( rollType === 'T-Mog' ) maxRoll = 25 ;
836+
837+ const playerColor = window . classColors [ roll . class . toUpperCase ( ) ] || '#FFFFFF' ;
838+ const isWinner = roll . player === awardedTo ;
839+ const fontWeight = isWinner ? 'font-bold text-lg' : 'font-normal' ;
840+ const rollColor = isWinner ? 'text-yellow-400' : 'text-gray-300' ;
841+
842+ return `<span class="whitespace-nowrap ${ fontWeight } "><span style="color:${ playerColor } ">${ roll . player } </span>: <span class="${ rollColor } ">${ roll . amount } </span> <span class="text-xs text-gray-500">(${ rollType } /${ maxRoll } )</span></span>` ;
843+ } ) . join ( ' ' ) ;
844+
845+ // --- NEU: Wowhead-Link erstellen ---
846+ // Item-Namen aus "[Item Name]" extrahieren
847+ const itemName = item . itemLink . replace ( / [ \[ \] ] / g, '' ) ;
848+ // Wowhead-URL für MoP Classic zusammenbauen
849+ const wowheadUrl = `https://www.wowhead.com/mop-classic/item=${ item . itemID } ` ;
850+ // Das komplette <a>-Tag für den Link erstellen
851+ const itemLinkHtml = `<a href="${ wowheadUrl } " target="_blank" rel="noopener noreferrer" class="hover:underline">${ itemName } </a>` ;
852+ // --- Ende der neuen Sektion ---
853+
854+ html += `
855+ <div class="bg-slate-750 p-4 rounded-lg mb-4">
856+ <div class="flex justify-between items-start">
857+ <h4 class="text-lg font-bold" style="color: var(--color-gold);">${ itemLinkHtml } </h4>
858+ <span class="text-sm text-gray-400">${ new Date ( item . timestamp * 1000 ) . toLocaleTimeString ( 'de-DE' ) } </span>
859+ </div>
860+ <p class="mt-1">Vergeben an: <strong style="color: ${ winnerColor } ;">${ awardedTo } </strong></p>
861+ <div class="mt-2 text-sm">
862+ <p class="font-semibold">Würfe:</p>
863+ <div class="flex flex-wrap gap-x-4 gap-y-1 mt-1">
864+ ${ rollsHtml }
865+ </div>
866+ </div>
867+ </div>
868+ ` ;
869+ } ) ;
870+
871+ displayContainer . innerHTML = html || '<p>Keine Items für dieses Datum gefunden.</p>' ;
872+ } ) ;
873+ }
734874
735875 // --- Logik für Boss-Seiten ---
736876 function initBossPage ( pageId ) {
@@ -835,7 +975,6 @@ <h3>Nachricht</h3>
835975
836976 function createPlayerElement ( player ) {
837977 const playerDiv = document . createElement ( 'div' ) ;
838- // Die Klasse für die responsive Anpassung bleibt erhalten
839978 playerDiv . className = 'flex justify-between items-center player-row-container' ;
840979 playerDiv . dataset . playerId = player . id ;
841980
@@ -881,7 +1020,7 @@ <h3>Nachricht</h3>
8811020 nameInput ?. addEventListener ( 'keydown' , ( event ) => {
8821021 if ( event . key === 'Enter' ) {
8831022 event . preventDefault ( ) ;
884- event . target . blur ( ) ; // Simuliert das Verlassen des Feldes
1023+ event . target . blur ( ) ;
8851024 }
8861025 } ) ;
8871026 const classSelect = element . querySelector ( '.editable-class-select' ) ;
0 commit comments