Spaces:
Sleeping
Sleeping
| <html lang="ro"> | |
| <head> | |
| <link rel="icon" type="image/svg+xml" href="favicon.svg"> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> | |
| <title>VSERVERS | Profesori</title> | |
| <link rel="stylesheet" href="style.css"> | |
| <style> | |
| /* ── LOADING SCREEN — foloseste loader-overlay din style.css ── */ | |
| /* ── LIQUID GLASS TOPBAR ── */ | |
| .topbar { | |
| background: rgba(15,15,15,0.55) !important; | |
| backdrop-filter: blur(32px) saturate(180%) !important; | |
| -webkit-backdrop-filter: blur(32px) saturate(180%) !important; | |
| border-bottom: 1px solid rgba(255,255,255,0.07) !important; | |
| box-shadow: 0 1px 0 rgba(255,255,255,0.04), 0 4px 20px rgba(0,0,0,0.4); | |
| } | |
| /* ── LIQUID GLASS CARDS ── */ | |
| .glass-card { | |
| background: rgba(255,255,255,0.04); | |
| backdrop-filter: blur(24px) saturate(160%); | |
| -webkit-backdrop-filter: blur(24px) saturate(160%); | |
| border: 1px solid rgba(255,255,255,0.09); | |
| box-shadow: | |
| 0 0 0 0.5px rgba(255,255,255,0.05) inset, | |
| 0 8px 32px rgba(0,0,0,0.35), | |
| 0 1px 0 rgba(255,255,255,0.06) inset; | |
| } | |
| /* Stats row glass */ | |
| .stats-row { | |
| background: transparent !important; | |
| border: none !important; | |
| gap: 8px !important; | |
| } | |
| .stat-box { | |
| border-radius: 2px; | |
| background: rgba(255,255,255,0.04) !important; | |
| backdrop-filter: blur(20px) saturate(150%); | |
| -webkit-backdrop-filter: blur(20px) saturate(150%); | |
| border: 1px solid rgba(255,255,255,0.08) !important; | |
| box-shadow: 0 4px 16px rgba(0,0,0,0.25), 0 1px 0 rgba(255,255,255,0.05) inset; | |
| } | |
| /* Table glass */ | |
| .data-table { | |
| background: rgba(255,255,255,0.02) !important; | |
| backdrop-filter: blur(16px); | |
| -webkit-backdrop-filter: blur(16px); | |
| border: 1px solid rgba(255,255,255,0.07) !important; | |
| box-shadow: 0 4px 24px rgba(0,0,0,0.3); | |
| } | |
| .dt-head { | |
| background: rgba(255,255,255,0.05) !important; | |
| border-bottom: 1px solid rgba(255,255,255,0.06) !important; | |
| } | |
| .dt-row { | |
| border-bottom: 1px solid rgba(255,255,255,0.04) !important; | |
| } | |
| .dt-row:hover { background: rgba(255,255,255,0.04) !important; } | |
| /* Drawer glass */ | |
| .drawer { | |
| background: rgba(255,255,255,0.03); | |
| backdrop-filter: blur(24px) saturate(160%); | |
| -webkit-backdrop-filter: blur(24px) saturate(160%); | |
| border: 1px solid rgba(255,255,255,0.08) !important; | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.4); | |
| } | |
| .drawer-head { | |
| background: rgba(255,255,255,0.05) !important; | |
| border-bottom: 1px solid rgba(255,255,255,0.07) !important; | |
| } | |
| /* Footer glass */ | |
| .footer { | |
| border-top: 1px solid rgba(255,255,255,0.06) !important; | |
| } | |
| /* Table layout */ | |
| .elev-row { grid-template-columns: 90px 1fr 56px 80px 90px; } | |
| @media(max-width:560px) { | |
| .elev-row { grid-template-columns: 1fr auto; } | |
| .col-cnt, .col-st { display: none; } | |
| } | |
| /* Drawer */ | |
| .drawer { display: none; margin-bottom: 16px; } | |
| .drawer.show { display: block; } | |
| .drawer-head { padding: 13px 14px; display: flex; align-items: center; gap: 12px; } | |
| .drawer-head h4 { font-family: 'Cormorant Garamond', serif; font-size: 17px; font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |
| .drawer-sub { font-size: 10px; color: var(--white-dim); letter-spacing: 1px; white-space: nowrap; } | |
| .drawer-close { cursor: pointer; color: var(--white-dim); font-size: 18px; transition: color 0.15s; flex-shrink: 0; padding: 2px; } | |
| .drawer-close:hover { color: #cc5555; } | |
| .drawer-body { padding: 16px 14px; } | |
| .no-files { font-size: 11px; color: var(--white-dim); letter-spacing: 1px; padding: 4px 0; } | |
| /* File row in drawer */ | |
| .file-row { | |
| background: rgba(255,255,255,0.03); | |
| border: 1px solid rgba(255,255,255,0.07) !important; | |
| } | |
| .file-row:hover { background: rgba(255,255,255,0.06) !important; border-color: rgba(255,255,255,0.12) !important; } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- LOADING SCREEN --> | |
| <div class="loader-overlay" id="loading-screen"> | |
| <div class="loader"><div class="inner one"></div><div class="inner two"></div><div class="inner three"></div></div> | |
| <div class="loader-text" id="load-status">SE ÎNCARCĂ</div> | |
| </div> | |
| <div class="topbar"> | |
| <a href="index.html" class="topbar-logo"> | |
| <img src="logo.svg" alt="VS"> | |
| <span class="topbar-name">VSERVERS</span> | |
| </a> | |
| <div class="topbar-divider"></div> | |
| <span class="topbar-section" id="tb-name">—</span> | |
| <div class="topbar-right"> | |
| <span class="role-tag">PROF</span> | |
| <span class="role-tag" id="tb-mat" style="color:var(--white);">—</span> | |
| <button class="btn-ghost" onclick="logout()">Iesire</button> | |
| </div> | |
| </div> | |
| <div class="main"> | |
| <div class="stats-row fade-in"> | |
| <div class="stat-box"> | |
| <div class="stat-num" id="sn" style="font-size:14px;padding-top:5px;">—</div> | |
| <div class="stat-lbl">PROFESOR</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-num" id="sm" style="font-size:16px;">—</div> | |
| <div class="stat-lbl">MATERIE</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-num" id="s-elevi">0</div> | |
| <div class="stat-lbl">ELEVI</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-num" id="s-files">0</div> | |
| <div class="stat-lbl">FISIERE</div> | |
| </div> | |
| </div> | |
| <div class="label fade-in-2">Registrul clasei — <span id="lbl-mat" style="color:var(--white);">—</span></div> | |
| <div class="data-table fade-in-2"> | |
| <div class="dt-head elev-row"> | |
| <div>VPASS ID</div><div>ELEV</div> | |
| <div class="col-cnt" style="text-align:center;">FIS.</div> | |
| <div class="col-st">STATUS</div> | |
| <div>ACTIUNI</div> | |
| </div> | |
| <div id="students-list"> | |
| <div style="padding:20px 14px;font-size:11px;color:var(--white-dim);">Se incarca...</div> | |
| </div> | |
| </div> | |
| <div class="drawer" id="drawer"> | |
| <div class="drawer-head"> | |
| <h4 id="d-name">—</h4> | |
| <span class="drawer-sub" id="d-vpass">—</span> | |
| <span class="drawer-close" onclick="closeDrawer()">✕</span> | |
| </div> | |
| <div class="drawer-body" id="d-body"> | |
| <div class="no-files">Se incarca fisierele...</div> | |
| </div> | |
| </div> | |
| <footer class="footer"> | |
| <div class="footer-top"> | |
| <img src="logo.svg" alt=""> | |
| <span>VSERVERS</span> | |
| </div> | |
| <div class="footer-divider"></div> | |
| <div class="footer-meta">93.117.161.226 — Telenești, Moldova</div> | |
| <div class="footer-copy"> | |
| © 2026 Victor Rosca — Toate drepturile rezervate<br> | |
| Sistem Educational de Gestiune a Fisierelor — v1.0 | |
| </div> | |
| </footer> | |
| </div> | |
| <script type="module"> | |
| import { initializeApp } from "https://www.gstatic.com/firebasejs/10.12.0/firebase-app.js"; | |
| import { getFirestore, collection, getDocs } from "https://www.gstatic.com/firebasejs/10.12.0/firebase-firestore.js"; | |
| const cfg = { | |
| apiKey:"AIzaSyB9--Onx3-_YjD-YzblhZjaWSVVqTQJ1lU", authDomain:"vservers1.firebaseapp.com", | |
| projectId:"vservers1", storageBucket:"vservers1.firebasestorage.app", | |
| messagingSenderId:"42433037358", appId:"1:42433037358:web:fde70fec79542428b60bbf" | |
| }; | |
| if (sessionStorage.getItem('vs_role') !== 'profesor') { window.location.href = 'index.html'; } | |
| const app = initializeApp(cfg); | |
| const db = getFirestore(app); | |
| const name = sessionStorage.getItem('vs_name') || '—'; | |
| const materie = sessionStorage.getItem('vs_materie') || '—'; | |
| document.getElementById('tb-name').textContent = name; | |
| document.getElementById('tb-mat').textContent = materie; | |
| document.getElementById('sn').textContent = name; | |
| document.getElementById('sm').textContent = materie; | |
| document.getElementById('lbl-mat').textContent = materie; | |
| function setStatus(msg) { | |
| document.getElementById('load-status').textContent = msg; | |
| } | |
| function hideLoader() { | |
| const el = document.getElementById('loading-screen'); | |
| el.classList.add('hide'); | |
| setTimeout(() => el.remove(), 600); | |
| } | |
| // Find materie ID | |
| setStatus('Se cauta materia...'); | |
| let materieId = ''; | |
| try { | |
| const mSnap = await getDocs(collection(db, 'materii')); | |
| mSnap.forEach(d => { if (d.data().nume === materie) materieId = d.id; }); | |
| } catch(e) {} | |
| // Load students — afiseaza lista imediat, fara sa astepte Drive | |
| setStatus('Se incarca elevii...'); | |
| let elevii = []; | |
| try { | |
| const snap = await getDocs(collection(db, 'elevi')); | |
| snap.forEach(d => elevii.push({ id: d.id, ...d.data() })); | |
| elevii.sort((a, b) => (a.pozitie || 0) - (b.pozitie || 0)); | |
| const list = document.getElementById('students-list'); list.innerHTML = ''; | |
| if (!elevii.length) { | |
| list.innerHTML = '<div style="padding:16px 14px;font-size:11px;color:var(--white-dim);">Niciun elev inregistrat.</div>'; | |
| } else { | |
| elevii.forEach(e => { | |
| const row = document.createElement('div'); row.className = 'dt-row elev-row'; | |
| row.innerHTML = ` | |
| <div style="font-size:10px;color:var(--white-dim);letter-spacing:1px;">${e.vpassId||'—'}</div> | |
| <div style="font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${e.nume}</div> | |
| <div class="col-cnt" id="cnt-${e.id}" style="text-align:center;font-size:12px;color:var(--white-faint);">—</div> | |
| <div class="col-st"><span class="pill empty" id="pill-${e.id}">ASTEPT</span></div> | |
| <div><button class="btn-outline" style="font-size:9px;padding:4px 10px;">VIZUALIZ.</button></div>`; | |
| row.querySelector('button').onclick = () => openDrawer(e.id, e.vpassId||e.id, e.nume, materieId, materie); | |
| list.appendChild(row); | |
| }); | |
| document.getElementById('s-elevi').textContent = elevii.length; | |
| } | |
| } catch(e) { console.error(e); } | |
| // Ascunde loader-ul INTOTDEAUNA, indiferent de ce s-a intamplat | |
| hideLoader(); | |
| // Incarca numarul de fisiere din Drive in fundal (pagina e deja vizibila) | |
| if (materieId && elevii.length) { | |
| let totalFiles = 0; | |
| for (const e of elevii) { | |
| try { | |
| const r = await fetch(`/drive/list?elevId=${encodeURIComponent(e.id)}&materieId=${encodeURIComponent(materieId)}`); | |
| const data = await r.json(); | |
| const cnt = (data.files || []).length; | |
| totalFiles += cnt; | |
| const cntEl = document.getElementById(`cnt-${e.id}`); | |
| const pillEl = document.getElementById(`pill-${e.id}`); | |
| if (cntEl) { cntEl.textContent = cnt; cntEl.style.color = cnt > 0 ? 'var(--white)' : 'var(--white-faint)'; } | |
| if (pillEl) { pillEl.textContent = cnt > 0 ? 'TRIMIS' : 'ASTEPT'; pillEl.className = `pill ${cnt > 0 ? 'success' : 'empty'}`; } | |
| } catch(err) {} | |
| } | |
| document.getElementById('s-files').textContent = totalFiles; | |
| } | |
| async function openDrawer(elevId, vpassId, elevNume, materieId, materie) { | |
| document.getElementById('d-name').textContent = elevNume; | |
| document.getElementById('d-vpass').textContent = vpassId + ' · ' + materie; | |
| const drawer = document.getElementById('drawer'); | |
| drawer.classList.add('show'); | |
| drawer.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); | |
| const body = document.getElementById('d-body'); | |
| body.innerHTML = '<div class="no-files">Se incarca...</div>'; | |
| if (!materieId) { body.innerHTML = '<div class="no-files">Materie neconfigurată.</div>'; return; } | |
| try { | |
| if (!materieId) { body.innerHTML = '<div class="no-files">Materie neconfigurată.</div>'; return; } | |
| const r = await fetch(`/drive/list?elevId=${encodeURIComponent(elevId)}&materieId=${encodeURIComponent(materieId)}`); | |
| const data = await r.json(); | |
| const files = data.files || []; | |
| if (!files.length) { body.innerHTML = '<div class="no-files">Niciun fisier pentru aceasta materie.</div>'; return; } | |
| body.innerHTML = ''; | |
| files.forEach(f => { | |
| const row = document.createElement('div'); row.className = 'file-row'; | |
| row.innerHTML = ` | |
| <svg class="fr-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"> | |
| <path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/><polyline points="13 2 13 9 20 9"/> | |
| </svg> | |
| <span class="fr-name">${f.name}</span> | |
| <span class="fr-meta">${f.size ? (parseInt(f.size)/1024/1024).toFixed(1)+' MB' : materie}</span> | |
| <div style="display:flex;gap:6px;"> | |
| <a href="/drive/download/${f.id}" class="btn-outline" style="font-size:9px;padding:4px 10px;text-decoration:none;">DESCARCĂ</a> | |
| <button class="btn-outline" style="font-size:9px;padding:4px 10px;color:#cc5555;border-color:rgba(200,80,80,0.3);" onclick="deleteFileProf('${f.id}', this, '${elevId}', '${materieId}', '${materie}', '${elevNume}')">ȘTERGE</button> | |
| </div>`; | |
| body.appendChild(row); | |
| }); | |
| } catch(e) { body.innerHTML = '<div class="no-files">Eroare la incarcare.</div>'; } | |
| } | |
| window.openDrawer = openDrawer; | |
| window.deleteFileProf = async function(fileId, btn, elevId, materieId, materie, elevNume) { | |
| if (!confirm(`Ștergi fișierul lui ${elevNume}?`)) return; | |
| btn.disabled = true; btn.textContent = '...'; | |
| try { | |
| const r = await fetch(`/drive/delete/${fileId}`, { method: 'DELETE' }); | |
| if (r.ok) { openDrawer(elevId, elevId, elevNume, materieId, materie); } | |
| else { btn.disabled = false; btn.textContent = 'ȘTERGE'; alert('Eroare la ștergere.'); } | |
| } catch(e) { btn.disabled = false; btn.textContent = 'ȘTERGE'; } | |
| }; | |
| </script> | |
| <script> | |
| function closeDrawer() { document.getElementById('drawer').classList.remove('show'); } | |
| function logout() { sessionStorage.clear(); window.location.href = 'index.html'; } | |
| </script> | |
| </body> | |
| </html> | |