| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Team Manager</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700;900&family=Inter:wght@300;400;600&display=swap" rel="stylesheet"> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| body { |
| font-family: 'Inter', sans-serif; |
| background-color: #0f172a; |
| color: white; |
| height: 100vh; |
| overflow: hidden; |
| touch-action: manipulation; |
| } |
| h1, h2, h3, .orbitron { font-family: 'Orbitron', sans-serif; } |
| |
| |
| ::-webkit-scrollbar { width: 6px; height: 6px; } |
| ::-webkit-scrollbar-track { background: #1e293b; } |
| ::-webkit-scrollbar-thumb { background: #475569; border-radius: 3px; } |
| |
| .glass-panel { |
| background: rgba(30, 41, 59, 0.7); |
| backdrop-filter: blur(10px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| |
| .roster-slot { |
| min-width: 120px; |
| max-width: 150px; |
| flex-shrink: 0; |
| aspect-ratio: 3/4; |
| transition: all 0.2s; |
| } |
| .roster-slot.drag-over { |
| transform: scale(1.05); |
| box-shadow: 0 0 20px rgba(59, 130, 246, 0.6); |
| border-color: #60a5fa; |
| border-style: solid; |
| } |
| |
| |
| .member-card { |
| aspect-ratio: 3/4; |
| cursor: grab; |
| transition: transform 0.2s, box-shadow 0.2s; |
| touch-action: none; |
| user-select: none; |
| } |
| .member-card:active { cursor: grabbing; transform: scale(0.98); } |
| .member-card:hover { |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5); |
| border-color: #3b82f6; |
| } |
| |
| |
| .spinner { |
| border: 3px solid rgba(255,255,255,0.3); |
| border-radius: 50%; |
| border-top: 3px solid #3b82f6; |
| width: 24px; |
| height: 24px; |
| animation: spin 1s linear infinite; |
| } |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| |
| @media (max-width: 768px) { |
| .roster-slot { min-width: 100px; } |
| #playersContainer { grid-template-columns: repeat(2, 1fr); } |
| } |
| |
| |
| .share-modal { |
| position: fixed; |
| inset: 0; |
| background: rgba(0, 0, 0, 0.8); |
| display: none; |
| align-items: center; |
| justify-content: center; |
| z-index: 1000; |
| } |
| .share-modal.active { |
| display: flex; |
| } |
| .share-content { |
| background: #1e293b; |
| border: 1px solid #475569; |
| border-radius: 12px; |
| padding: 24px; |
| width: 90%; |
| max-width: 500px; |
| } |
| </style> |
| </head> |
| <body class="flex"> |
|
|
| |
| <div id="sidebar" class="w-1/3 h-full flex flex-col border-r border-slate-700 bg-slate-900"> |
| <div class="p-6 border-b border-slate-700 bg-slate-900/50"> |
| <h1 class="text-xl font-bold tracking-widest text-cyan-400 orbitron">TEAMS DASHBOARD</h1> |
| <button onclick="createNewTeam()" class="mt-4 w-full py-3 bg-blue-600 hover:bg-blue-500 text-white font-bold rounded uppercase tracking-wider transition-colors flex items-center justify-center gap-2"> |
| <i class="fas fa-plus"></i> |
| Create New Team |
| </button> |
| </div> |
| |
| <div id="teamsList" class="flex-1 overflow-y-auto p-2 space-y-1"> |
| <div class="text-center text-gray-500 mt-10">Loading teams...</div> |
| </div> |
| </div> |
|
|
| |
| <div id="mainPanel" class="w-2/3 h-full flex flex-col bg-slate-800/50 relative"> |
| <div id="emptyState" class="absolute inset-0 flex items-center justify-center text-slate-500 pointer-events-none"> |
| <div class="text-center"> |
| <i class="fas fa-users text-6xl mb-4 opacity-50"></i> |
| <p class="text-lg orbitron">Select a team to edit</p> |
| </div> |
| </div> |
|
|
| <div id="editorPanel" class="hidden flex-col h-full"> |
| <div class="p-4 md:p-6 border-b border-slate-700 bg-slate-900/80 flex justify-between items-center"> |
| <h2 id="editorTitle" class="text-lg md:text-2xl font-bold text-white orbitron">Edit Team</h2> |
| <div class="flex gap-2 md:gap-3"> |
| <button onclick="openShareModal()" id="shareBtn" class="px-4 py-2 bg-purple-600 hover:bg-purple-500 text-white font-bold rounded text-sm tracking-widest flex items-center gap-2"> |
| <i class="fas fa-share-alt"></i> |
| <span class="hidden md:inline">SHARE</span> |
| </button> |
| <button onclick="saveTeam()" id="saveBtn" class="px-4 md:px-6 py-2 bg-green-600 hover:bg-green-500 text-white font-bold rounded shadow-[0_0_15px_rgba(22,163,74,0.4)] text-sm tracking-widest"> |
| SAVE |
| </button> |
| </div> |
| </div> |
|
|
| <div class="flex-1 overflow-y-auto p-4 md:p-8 space-y-6 md:space-y-8"> |
| |
| <div class="glass-panel p-4 md:p-6 rounded-xl"> |
| <h3 class="text-sm font-bold text-cyan-400 uppercase tracking-widest mb-4">Team Info & Active Roster</h3> |
| |
| |
| <div class="flex flex-col md:flex-row gap-4 md:gap-6 mb-6 md:mb-8"> |
| <div class="w-20 h-20 md:w-24 md:h-24 shrink-0 relative group self-center md:self-start"> |
| |
| <input type="file" id="teamLogoInput" class="hidden" accept="image/*" onchange="handleInstantUpload(this, 'teamLogoPreview', 'teamLogoLoader', 'teamLogoPlaceholder')"> |
| |
| <div class="w-full h-full rounded-lg cursor-pointer flex items-center justify-center relative overflow-hidden border border-slate-600 bg-slate-800" onclick="document.getElementById('teamLogoInput').click()"> |
| <img id="teamLogoPreview" src="" class="w-full h-full object-cover hidden"> |
| <div id="teamLogoPlaceholder" class="text-center text-xs text-gray-400"> |
| <i class="fas fa-image text-lg mb-1 block"></i> |
| Logo |
| </div> |
| |
| <div id="teamLogoLoader" class="hidden absolute inset-0 bg-black/70 flex items-center justify-center z-10"> |
| <div class="spinner"></div> |
| </div> |
| </div> |
| </div> |
| <div class="flex-1"> |
| <label class="block text-xs text-gray-400 mb-1 font-bold tracking-wider">TEAM NAME</label> |
| <input type="text" id="teamNameInput" class="w-full bg-slate-900 border border-slate-700 rounded p-3 text-white focus:border-cyan-500 outline-none font-bold text-lg" placeholder="e.g. Los Angeles Lakers"> |
| </div> |
| </div> |
|
|
| |
| <div> |
| <div class="flex flex-col md:flex-row md:justify-between md:items-end mb-2"> |
| <label class="block text-xs text-gray-400 uppercase tracking-widest mb-1 md:mb-0">Active Roster (Drag from Members below)</label> |
| <span class="text-xs text-gray-500">Limits: 1 Coach, 2 Strikers, 1 Others</span> |
| </div> |
| |
| |
| <div class="flex gap-3 overflow-x-auto pb-4" id="rosterScrollContainer"> |
| |
| <div class="roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative" |
| ondrop="drop(event)" ondragover="allowDrop(event)" |
| ontouchstart="touchStart(event)" ontouchmove="touchMove(event)" ontouchend="touchEnd(event)" |
| id="slot-0"></div> |
| <div class="roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative" |
| ondrop="drop(event)" ondragover="allowDrop(event)" |
| ontouchstart="touchStart(event)" ontouchmove="touchMove(event)" ontouchend="touchEnd(event)" |
| id="slot-1"></div> |
| <div class="roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative" |
| ondrop="drop(event)" ondragover="allowDrop(event)" |
| ontouchstart="touchStart(event)" ontouchmove="touchMove(event)" ontouchend="touchEnd(event)" |
| id="slot-2"></div> |
| <div class="roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative" |
| ondrop="drop(event)" ondragover="allowDrop(event)" |
| ontouchstart="touchStart(event)" ontouchmove="touchMove(event)" ontouchend="touchEnd(event)" |
| id="slot-3"></div> |
| <div class="roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative" |
| ondrop="drop(event)" ondragover="allowDrop(event)" |
| ontouchstart="touchStart(event)" ontouchmove="touchMove(event)" ontouchend="touchEnd(event)" |
| id="slot-4"></div> |
| <div class="roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative" |
| ondrop="drop(event)" ondragover="allowDrop(event)" |
| ontouchstart="touchStart(event)" ontouchmove="touchMove(event)" ontouchend="touchEnd(event)" |
| id="slot-5"></div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="glass-panel p-4 md:p-6 rounded-xl"> |
| <div class="flex justify-between items-center mb-4"> |
| <h3 class="text-sm font-bold text-cyan-400 uppercase tracking-widest">MEMBERS</h3> |
| <button onclick="addPlayerRow()" class="text-xs bg-slate-700 hover:bg-slate-600 px-3 py-1 rounded text-white flex items-center gap-1"> |
| <i class="fas fa-plus"></i> |
| Add Member |
| </button> |
| </div> |
| |
| <div id="playersContainer" class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-3 md:gap-4"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="shareModal" class="share-modal"> |
| <div class="share-content"> |
| <div class="flex justify-between items-center mb-4"> |
| <h3 class="text-xl font-bold orbitron">Share Team</h3> |
| <button onclick="closeShareModal()" class="text-gray-400 hover:text-white"> |
| <i class="fas fa-times text-2xl"></i> |
| </button> |
| </div> |
| <p class="text-gray-300 mb-4">Share this link to allow others to edit this team:</p> |
| <div class="flex gap-2 mb-4"> |
| <input type="text" id="shareUrl" readonly class="flex-1 bg-slate-900 border border-slate-700 rounded p-3 text-white text-sm" value="Loading..."> |
| <button onclick="copyShareUrl()" id="copyBtn" class="bg-blue-600 hover:bg-blue-500 text-white px-4 rounded font-bold"> |
| <i class="fas fa-copy"></i> |
| </button> |
| </div> |
| <div class="text-sm text-gray-400"> |
| <p><i class="fas fa-info-circle mr-2"></i>Anyone with this link can edit the team</p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="notification" class="fixed bottom-6 right-6 px-6 py-4 bg-slate-900 border border-slate-700 rounded-lg shadow-2xl transform translate-y-20 transition-transform duration-300 flex items-center gap-3 z-50"> |
| <span id="notifMsg" class="font-medium text-sm"></span> |
| </div> |
|
|
| |
| <script type="module"> |
| import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js"; |
| import { getDatabase, ref, set, push, onValue, remove, update } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-database.js"; |
| |
| const firebaseConfig = { |
| apiKey: "AIzaSyDi6znbXgOFR7aXZ9v7aVFP065yE98T7XU", |
| authDomain: "fida-kz.firebaseapp.com", |
| databaseURL: "https://fida-kz-default-rtdb.firebaseio.com", |
| projectId: "fida-kz", |
| appId: "1:728249202318:web:29ee414ef7eff47573fbdf" |
| }; |
| |
| const CLOUD_NAME = "dkagzakrx"; |
| const API_KEY = "369873383773423"; |
| const API_SECRET = "OWUfaIwcl0nJYfqZxd7d1lBQkJA"; |
| const CLOUDINARY_URL = `https://api.cloudinary.com/v1_1/${CLOUD_NAME}/image/upload`; |
| |
| const app = initializeApp(firebaseConfig); |
| const db = getDatabase(app); |
| |
| |
| window.currentTeamId = null; |
| window.teamsCache = {}; |
| window.isSharedMode = false; |
| |
| |
| let touchDragData = null; |
| let touchStartX = 0; |
| let touchStartY = 0; |
| let touchStartTime = 0; |
| |
| |
| const urlParams = new URLSearchParams(window.location.search); |
| const sharedTeamId = urlParams.get('teamId'); |
| |
| if (sharedTeamId) { |
| |
| window.isSharedMode = true; |
| document.getElementById('sidebar').style.display = 'none'; |
| document.getElementById('mainPanel').style.width = '100%'; |
| loadSharedTeam(sharedTeamId); |
| } |
| |
| |
| async function sha1(str) { |
| const enc = new TextEncoder(); |
| const hash = await crypto.subtle.digest('SHA-1', enc.encode(str)); |
| return Array.from(new Uint8Array(hash)).map(v => v.toString(16).padStart(2, '0')).join(''); |
| } |
| |
| async function uploadToCloudinary(file) { |
| const timestamp = Math.floor(Date.now() / 1000); |
| const signature = await sha1(`timestamp=${timestamp}${API_SECRET}`); |
| const formData = new FormData(); |
| formData.append("file", file); |
| formData.append("api_key", API_KEY); |
| formData.append("timestamp", timestamp); |
| formData.append("signature", signature); |
| const res = await fetch(CLOUDINARY_URL, { method: "POST", body: formData }); |
| if (!res.ok) throw new Error("Upload failed"); |
| const data = await res.json(); |
| return data.secure_url; |
| } |
| |
| |
| window.handleInstantUpload = async function(input, imgId, loaderId, placeholderId) { |
| if (!input.files || !input.files[0]) return; |
| |
| const loader = document.getElementById(loaderId); |
| const img = document.getElementById(imgId); |
| const placeholder = document.getElementById(placeholderId); |
| |
| if (loader) loader.classList.remove('hidden'); |
| |
| try { |
| const url = await uploadToCloudinary(input.files[0]); |
| |
| img.src = url; |
| img.setAttribute('data-url', url); |
| img.classList.remove('hidden'); |
| |
| if (placeholder) placeholder.classList.add('hidden'); |
| } catch (e) { |
| console.error(e); |
| showNotif("Upload failed: " + e.message, "error"); |
| input.value = ""; |
| } finally { |
| if (loader) loader.classList.add('hidden'); |
| } |
| } |
| |
| |
| window.touchStart = function(event) { |
| touchStartX = event.touches[0].clientX; |
| touchStartY = event.touches[0].clientY; |
| touchStartTime = Date.now(); |
| event.preventDefault(); |
| } |
| |
| window.touchMove = function(event) { |
| event.preventDefault(); |
| } |
| |
| window.touchEnd = function(event) { |
| const touchEndX = event.changedTouches[0].clientX; |
| const touchEndY = event.changedTouches[0].clientY; |
| const touchEndTime = Date.now(); |
| |
| |
| const distX = Math.abs(touchEndX - touchStartX); |
| const distY = Math.abs(touchEndY - touchStartY); |
| const timeDiff = touchEndTime - touchStartTime; |
| |
| |
| if (distX < 10 && distY < 10 && timeDiff < 500 && touchDragData) { |
| const slot = event.target.closest('.roster-slot'); |
| if (slot) { |
| handleDrop(slot, touchDragData); |
| touchDragData = null; |
| } |
| } |
| event.preventDefault(); |
| } |
| |
| |
| const ROLE_LIMITS = { 'coach': 1, 'striker': 2, 'keeper': 1, 'sweeper': 1, 'libero': 1, 'guide': 1 }; |
| |
| window.allowDrop = function(ev) { |
| ev.preventDefault(); |
| const slot = ev.target.closest('.roster-slot'); |
| if (slot) slot.classList.add('drag-over'); |
| } |
| |
| document.addEventListener('dragleave', (ev) => { |
| if (ev.target.classList.contains('roster-slot')) { |
| ev.target.classList.remove('drag-over'); |
| } |
| }); |
| |
| window.drag = function(ev) { |
| const card = ev.target.closest('.member-card'); |
| const name = card.querySelector('.name-in').value; |
| const role = card.querySelector('.role-in').value; |
| |
| |
| const imgObj = card.querySelector('.member-img'); |
| let img = ''; |
| if (imgObj.getAttribute('data-url')) { |
| img = imgObj.getAttribute('data-url'); |
| } else if (imgObj.src && imgObj.src !== window.location.href && !imgObj.src.startsWith('data:')) { |
| img = imgObj.src; |
| } |
| |
| const id = card.getAttribute('data-id') || 'temp_' + Date.now(); |
| |
| if (!name) { ev.preventDefault(); return; } |
| |
| |
| const payload = JSON.stringify({ id, name, role, img }); |
| ev.dataTransfer.setData("text/plain", payload); |
| |
| |
| touchDragData = { id, name, role, img }; |
| } |
| |
| window.drop = function(ev) { |
| ev.preventDefault(); |
| const slot = ev.target.closest('.roster-slot'); |
| slot.classList.remove('drag-over'); |
| try { |
| const data = JSON.parse(ev.dataTransfer.getData("text/plain")); |
| handleDrop(slot, data); |
| } catch (e) { console.error(e); } |
| } |
| |
| function handleDrop(slot, data) { |
| const allSlots = document.querySelectorAll('.roster-slot'); |
| |
| |
| for (let s of allSlots) { |
| if (s.getAttribute('data-player-id') === data.id) { |
| showNotif("Player already in roster!", "error"); |
| return; |
| } |
| } |
| |
| |
| let currentRoleCount = 0; |
| allSlots.forEach(s => { |
| if (s.getAttribute('data-role') === data.role) currentRoleCount++; |
| }); |
| |
| if (currentRoleCount >= ROLE_LIMITS[data.role]) { |
| showNotif(`Limit reached: Max ${ROLE_LIMITS[data.role]} ${data.role}(s)`, "error"); |
| return; |
| } |
| |
| renderSlot(slot, data); |
| } |
| |
| |
| function renderSlot(slot, data) { |
| slot.setAttribute('data-player-id', data.id); |
| slot.setAttribute('data-role', data.role); |
| |
| let roleColor = "bg-slate-700"; |
| if (data.role === 'coach') roleColor = "bg-yellow-900/50 border-yellow-700"; |
| else if (data.role === 'striker') roleColor = "bg-red-900/50 border-red-700"; |
| else if (data.role === 'keeper') roleColor = "bg-green-900/50 border-green-700"; |
| else if (data.role === 'guide') roleColor = "bg-blue-900/50 border-blue-700"; |
| else if (data.role === 'sweeper') roleColor = "bg-purple-900/50 border-purple-700"; |
| else if (data.role === 'libero') roleColor = "bg-orange-900/50 border-orange-700"; |
| |
| const imageUrl = data.img || data.image || ""; |
| |
| let imgHtml = ''; |
| if (imageUrl && imageUrl.startsWith('http')) { |
| imgHtml = `<img src="${imageUrl}" class="absolute inset-0 w-full h-full object-cover opacity-80 z-0">`; |
| } |
| |
| slot.className = `roster-slot ${roleColor} border-2 border-solid rounded-xl flex flex-col justify-end relative overflow-hidden group transition-all`; |
| slot.innerHTML = ` |
| ${imgHtml} |
| <div class="relative z-10 text-center w-full bg-gradient-to-t from-black via-black/80 to-transparent pt-6 pb-2 px-1"> |
| <div class="text-[9px] font-bold uppercase tracking-widest text-cyan-400 mb-0.5">${data.role}</div> |
| <div class="text-xs font-bold text-white truncate w-full leading-tight shadow-black drop-shadow-md">${data.name}</div> |
| </div> |
| <button onclick="clearSlot(this)" class="absolute top-1 right-1 bg-black/60 hover:bg-red-600 text-white rounded-full w-5 h-5 flex items-center justify-center text-[10px] opacity-0 group-hover:opacity-100 transition-opacity z-20">✕</button> |
| `; |
| } |
| |
| window.clearSlot = function(btn) { |
| const slot = btn.closest('.roster-slot'); |
| slot.removeAttribute('data-player-id'); |
| slot.removeAttribute('data-role'); |
| slot.className = "roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative"; |
| slot.innerHTML = ""; |
| event.stopPropagation(); |
| } |
| |
| |
| onValue(ref(db, 'teams'), (snapshot) => { |
| const data = snapshot.val(); |
| window.teamsCache = data || {}; |
| |
| if (!window.isSharedMode) { |
| renderTeamsList(window.teamsCache); |
| } |
| }); |
| |
| function renderTeamsList(teams) { |
| const list = document.getElementById('teamsList'); |
| list.innerHTML = ""; |
| |
| if (!teams || Object.keys(teams).length === 0) { |
| return list.innerHTML = `<div class="text-center text-gray-500 mt-10">No teams found.</div>`; |
| } |
| |
| Object.entries(teams).forEach(([key, team]) => { |
| const isActive = window.currentTeamId === key; |
| list.insertAdjacentHTML('beforeend', ` |
| <div onclick="loadTeamEditor('${key}')" class="p-4 cursor-pointer hover:bg-slate-800 transition-colors flex items-center gap-3 border-l-4 border-transparent ${isActive ? 'active-item bg-slate-800 border-blue-500' : ''}"> |
| <img src="${team.logo || 'https://via.placeholder.com/40?text=?'}" class="w-10 h-10 rounded bg-black object-cover"> |
| <div class="font-bold text-sm">${team.name || 'Unnamed'}</div> |
| </div>`); |
| }); |
| } |
| |
| |
| async function loadSharedTeam(teamId) { |
| const teamRef = ref(db, `teams/${teamId}`); |
| onValue(teamRef, (snapshot) => { |
| const team = snapshot.val(); |
| if (team) { |
| window.currentTeamId = teamId; |
| loadTeamData(team); |
| document.title = `Editing: ${team.name || 'Team'}`; |
| } else { |
| showNotif("Team not found", "error"); |
| } |
| }); |
| } |
| |
| window.createNewTeam = function() { |
| window.currentTeamId = null; |
| resetUI("Create New Team", true); |
| document.getElementById('playersContainer').innerHTML = ""; |
| window.addPlayerRow(); |
| renderTeamsList(window.teamsCache); |
| } |
| |
| window.loadTeamEditor = function(teamId) { |
| window.currentTeamId = teamId; |
| const team = window.teamsCache[teamId]; |
| if (!team) return; |
| loadTeamData(team); |
| } |
| |
| function loadTeamData(team) { |
| resetUI("Edit Team", false); |
| document.getElementById('teamNameInput').value = team.name || ""; |
| |
| const logoImg = document.getElementById('teamLogoPreview'); |
| const logoPlace = document.getElementById('teamLogoPlaceholder'); |
| if (team.logo) { |
| logoImg.src = team.logo; |
| logoImg.setAttribute('data-url', team.logo); |
| logoImg.classList.remove('hidden'); |
| logoPlace.classList.add('hidden'); |
| } else { |
| logoImg.classList.add('hidden'); |
| logoPlace.classList.remove('hidden'); |
| } |
| |
| const container = document.getElementById('playersContainer'); |
| container.innerHTML = ""; |
| if (team.members) { |
| Object.entries(team.members).forEach(([uid, m]) => window.addPlayerRow({ ...m, id: uid })); |
| } else { |
| window.addPlayerRow(); |
| } |
| |
| const slots = document.querySelectorAll('.roster-slot'); |
| slots.forEach(slot => { |
| slot.className = "roster-slot bg-slate-900/50 border-2 border-dashed border-slate-700 rounded-xl flex items-center justify-center relative"; |
| slot.innerHTML = ""; |
| slot.removeAttribute('data-player-id'); |
| slot.removeAttribute('data-role'); |
| }); |
| |
| if (team.active_roster) { |
| team.active_roster.forEach((m, i) => { |
| if (i < 6 && m.id) renderSlot(slots[i], m); |
| }); |
| } |
| |
| if (!window.isSharedMode) { |
| renderTeamsList(window.teamsCache); |
| } |
| } |
| |
| window.resetUI = function(title, isNew) { |
| document.getElementById('emptyState').classList.add('hidden'); |
| document.getElementById('editorPanel').classList.remove('hidden'); |
| document.getElementById('editorPanel').classList.add('flex'); |
| document.getElementById('editorTitle').innerText = title; |
| } |
| |
| |
| window.openShareModal = function() { |
| if (!window.currentTeamId) { |
| showNotif("No team selected", "error"); |
| return; |
| } |
| |
| const shareUrl = `${window.location.origin}${window.location.pathname}?teamId=${window.currentTeamId}`; |
| document.getElementById('shareUrl').value = shareUrl; |
| document.getElementById('shareModal').classList.add('active'); |
| } |
| |
| window.closeShareModal = function() { |
| document.getElementById('shareModal').classList.remove('active'); |
| } |
| |
| window.copyShareUrl = function() { |
| const shareUrlInput = document.getElementById('shareUrl'); |
| shareUrlInput.select(); |
| shareUrlInput.setSelectionRange(0, 99999); |
| |
| navigator.clipboard.writeText(shareUrlInput.value).then(() => { |
| const copyBtn = document.getElementById('copyBtn'); |
| const originalHtml = copyBtn.innerHTML; |
| copyBtn.innerHTML = '<i class="fas fa-check"></i>'; |
| copyBtn.classList.add('bg-green-600'); |
| |
| setTimeout(() => { |
| copyBtn.innerHTML = originalHtml; |
| copyBtn.classList.remove('bg-green-600'); |
| }, 2000); |
| |
| showNotif("Link copied to clipboard", "success"); |
| }).catch(err => { |
| console.error('Failed to copy: ', err); |
| showNotif("Failed to copy link", "error"); |
| }); |
| } |
| |
| |
| window.saveTeam = async function() { |
| const btn = document.getElementById('saveBtn'); |
| const nameInput = document.getElementById('teamNameInput'); |
| if (!nameInput.value.trim()) return showNotif("Team name required", "error"); |
| |
| btn.disabled = true; |
| btn.innerHTML = '<div class="spinner"></div>'; |
| |
| try { |
| let teamId = window.currentTeamId || push(ref(db, 'teams')).key; |
| |
| |
| const logoImg = document.getElementById('teamLogoPreview'); |
| let finalLogo = logoImg.getAttribute('data-url') || (logoImg.src.startsWith('http') ? logoImg.src : ""); |
| if (finalLogo === window.location.href) finalLogo = ""; |
| |
| |
| const cards = document.querySelectorAll('.member-card'); |
| const membersObj = {}; |
| |
| cards.forEach(card => { |
| const name = card.querySelector('.name-in').value; |
| if (!name) return; |
| const role = card.querySelector('.role-in').value; |
| const imgObj = card.querySelector('.member-img'); |
| |
| let photo = imgObj.getAttribute('data-url') || ""; |
| if (!photo && imgObj.src.startsWith('http') && imgObj.src !== window.location.href) { |
| photo = imgObj.src; |
| } |
| |
| const mId = card.getAttribute('data-id') || 'mem_' + Date.now() + Math.random().toString(36).substr(2, 5); |
| card.setAttribute('data-id', mId); |
| |
| membersObj[mId] = { name, role, image: photo }; |
| }); |
| |
| |
| const activeRosterArr = []; |
| document.querySelectorAll('.roster-slot').forEach(slot => { |
| const pid = slot.getAttribute('data-player-id'); |
| if (pid && membersObj[pid]) { |
| activeRosterArr.push({ id: pid, ...membersObj[pid] }); |
| } |
| }); |
| |
| await update(ref(db, `teams/${teamId}`), { |
| name: nameInput.value, |
| logo: finalLogo, |
| members: membersObj, |
| active_roster: activeRosterArr |
| }); |
| |
| window.currentTeamId = teamId; |
| showNotif("Saved Successfully!", "success"); |
| |
| if (!window.isSharedMode) { |
| loadTeamEditor(teamId); |
| } |
| |
| } catch (e) { |
| console.error(e); |
| showNotif("Error: " + e.message, "error"); |
| } |
| finally { |
| btn.disabled = false; |
| btn.innerHTML = 'SAVE'; |
| } |
| } |
| |
| |
| window.addPlayerRow = function(p = null) { |
| const container = document.getElementById('playersContainer'); |
| const uid = Math.random().toString(36).slice(2); |
| const pid = p ? p.id : ''; |
| const purl = p ? p.image : ''; |
| |
| const roles = ['striker','guide','keeper','sweeper','libero','coach']; |
| const opts = roles.map(r => `<option value="${r}" ${p && p.role===r?'selected':''}>${r.toUpperCase()}</option>`).join(''); |
| |
| const html = ` |
| <div id="row-${uid}" draggable="true" ondragstart="drag(event)" |
| ontouchstart="drag(event)" |
| class="member-card aspect-[3/4] bg-slate-800 border border-slate-700 rounded-xl flex flex-col relative group overflow-hidden" |
| data-id="${pid}"> |
| |
| <button onclick="document.getElementById('row-${uid}').remove()" |
| class="absolute top-2 right-2 z-20 bg-black/60 hover:bg-red-600 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-opacity"> |
| ✕ |
| </button> |
| |
| <div class="h-[65%] w-full relative cursor-pointer bg-slate-900 group-inner" onclick="document.getElementById('file-${uid}').click()"> |
| <!-- IMAGE --> |
| <img id="img-${uid}" src="${purl}" |
| data-url="${purl}" |
| class="member-img w-full h-full object-cover ${purl?'':'hidden'}"> |
| |
| <!-- PLACEHOLDER --> |
| <div id="ph-${uid}" class="absolute inset-0 flex flex-col items-center justify-center text-slate-500 ${purl?'hidden':''}"> |
| <div class="text-3xl mb-1">+</div> |
| <span class="text-[10px] uppercase">Photo</span> |
| </div> |
| |
| <!-- LOADER --> |
| <div id="loader-${uid}" class="hidden absolute inset-0 bg-black/70 flex items-center justify-center z-30"> |
| <div class="spinner"></div> |
| </div> |
| </div> |
| |
| <!-- Input calls handleInstantUpload on change --> |
| <input type="file" id="file-${uid}" class="file-in hidden" accept="image/*" |
| onchange="handleInstantUpload(this, 'img-${uid}', 'loader-${uid}', 'ph-${uid}')"> |
| |
| <div class="h-[35%] bg-slate-950 p-2 flex flex-col justify-center gap-2 border-t border-slate-700"> |
| <input type="text" class="name-in w-full bg-transparent text-center font-bold text-white text-sm focus:outline-none border-b border-transparent focus:border-blue-500 placeholder-slate-600 pb-1" |
| placeholder="Name" value="${p?p.name:''}"> |
| <select class="role-in w-full bg-slate-900 text-[10px] text-center text-cyan-400 border border-slate-800 rounded py-1 outline-none uppercase tracking-wider"> |
| ${opts} |
| </select> |
| </div> |
| </div>`; |
| container.insertAdjacentHTML('beforeend', html); |
| } |
| |
| window.showNotif = function(msg, type) { |
| const n = document.getElementById('notification'); |
| document.getElementById('notifMsg').innerText = msg; |
| n.className = `fixed bottom-6 right-6 px-6 py-4 border rounded-lg shadow-2xl transform translate-y-20 transition-transform duration-300 flex items-center gap-3 z-50 ${type==='error'?'bg-red-900/90 border-red-700':'bg-green-900/90 border-green-700'}`; |
| n.classList.remove('translate-y-20'); |
| setTimeout(() => n.classList.add('translate-y-20'), 3000); |
| } |
| |
| |
| document.addEventListener('keydown', (e) => { |
| if (e.key === 'Escape') closeShareModal(); |
| }); |
| |
| |
| document.getElementById('shareModal').addEventListener('click', (e) => { |
| if (e.target.id === 'shareModal') closeShareModal(); |
| }); |
| </script> |
| </body> |
| </html> |
|
|