📅
LeaveSync load ho raha hai...

-

// ── STATE ── let CU = null; // current user object let USERS = []; // in-memory users cache let LEAVES = []; // in-memory leaves cache let cals = {}; // fullcalendar instances let rf = 'all'; // request filter let unsubListeners = []; // firestore realtime unsubscribe fns const COLS = ['#7c3aed','#2563eb','#059669','#d97706','#dc2626','#0891b2','#9333ea','#c2410c','#0284c7','#65a30d']; const ADMIN_DEFAULT = { id:'admin_001', name:'Admin', email:'admin@company.com', password:'admin123', role:'admin', color:'#7c3aed' }; // ── HELPERS ── const uid = () => '_' + Math.random().toString(36).substr(2, 9) + Date.now(); const ini = n => (n||'?').split(' ').map(c=>c[0]).join('').toUpperCase().slice(0, 2); const tod = () => new Date().toISOString().split('T')[0]; const fmD = d => d ? new Date(d+'T00:00:00').toLocaleDateString('en-IN', {weekday:'short',day:'numeric',month:'long',year:'numeric'}) : '---'; const fmS = d => d ? new Date(d).toLocaleDateString('en-IN', {day:'numeric',month:'short',year:'numeric'}) : '---'; const gCol = id => { const u=USERS.find(x=>x.id===id); return u?.color||'#7c3aed'; }; const sbdg = s => { const m={pending:'⏳ Pending',approved:'✅ Approved',rejected:'❌ Rejected'}; return `${m[s]||s}`; }; const raf = fn => requestAnimationFrame(() => setTimeout(fn, 30)); const hide = id => { const el=document.getElementById(id); if(el) el.style.display='none'; }; const show = (id,d='flex') => { const el=document.getElementById(id); if(el) el.style.display=d; }; // ── TOAST ── function toast(msg, type='info') { const w = document.getElementById('twrap'); const el = document.createElement('div'); el.className = 'toast ' + type; el.innerHTML = msg; w.appendChild(el); setTimeout(() => { el.style.transition = '.3s'; el.style.opacity = '0'; el.style.transform = 'translateX(110%)'; setTimeout(() => el.remove(), 300); }, 3500); } // ── MODAL ── function openM(title, html) { document.getElementById('mti').textContent = title; document.getElementById('mb').innerHTML = html; document.getElementById('mow').classList.add('show'); } function closeM() { document.getElementById('mow').classList.remove('show'); } function moClick(e) { if (e.target === document.getElementById('mow')) closeM(); } // ── LOADING ── function showLoading() { show('loading-screen','flex'); } function hideLoading() { hide('loading-screen'); } // ════════════════════════════════════ // FIRESTORE OPERATIONS // ════════════════════════════════════ const FS = { // Get all documents from a collection getAll: async (col) => { const snap = await db.collection(col).get(); return snap.docs.map(d => ({ id: d.id, ...d.data() })); }, // Get document by id getById: async (col, id) => { const doc = await db.collection(col).doc(id).get(); return doc.exists ? { id: doc.id, ...doc.data() } : null; }, // Query with where clause where: async (col, field, op, val) => { const snap = await db.collection(col).where(field, op, val).get(); return snap.docs.map(d => ({ id: d.id, ...d.data() })); }, // Set document with specific id set: async (col, id, data) => { return db.collection(col).doc(id).set(data); }, // Add document (auto id) add: async (col, data) => { const ref = await db.collection(col).add(data); return { id: ref.id, ...data }; }, // Update specific fields update: async (col, id, data) => { return db.collection(col).doc(id).update(data); }, // Delete document del: async (col, id) => { return db.collection(col).doc(id).delete(); }, // Real-time listener listen: (col, callback, query) => { let ref = db.collection(col); if (query) ref = ref.where(query.field, query.op, query.val); return ref.onSnapshot(snap => { const docs = snap.docs.map(d => ({ id: d.id, ...d.data() })); callback(docs); }); } }; // ════════════════════════════════════ // BOOT / INIT // ════════════════════════════════════ async function boot() { if (!CONFIG_SET) { hideLoading(); // Show setup warning on login page document.getElementById('lerr').style.display = 'block'; document.getElementById('lerr').innerHTML = ` ⚠️ Firebase config set nahi hai!
index.html mein firebaseConfig apna project config se update karo.
Firebase Console kholo →`; document.getElementById('login-btn').disabled = true; show('lp','flex'); return; } try { // Load all data first await Promise.all([loadUsers(), loadLeaves()]); // Seed admin if not exists const adminExists = USERS.find(u => u.role === 'admin'); if (!adminExists) { await FS.set('users', ADMIN_DEFAULT.id, ADMIN_DEFAULT); USERS.push(ADMIN_DEFAULT); } hideLoading(); // Check for saved session const saved = localStorage.getItem('lms_session'); if (saved) { const parsed = JSON.parse(saved); const fresh = USERS.find(u => u.id === parsed.id); if (fresh) { CU = fresh; mountApp(); return; } else { localStorage.removeItem('lms_session'); } } show('lp','flex'); } catch(err) { hideLoading(); show('lp','flex'); document.getElementById('lerr').style.display = 'block'; document.getElementById('lerr').textContent = '⚠ Firebase connect nahi ho pa raha. Config check karo. Error: ' + err.message; } } async function loadUsers() { USERS = await FS.getAll('users'); } async function loadLeaves() { LEAVES = await FS.getAll('leaves'); } // ── REAL-TIME LISTENERS ── function startListeners() { // Stop old listeners unsubListeners.forEach(fn => fn()); unsubListeners = []; // Listen to users const u1 = FS.listen('users', docs => { USERS = docs; refreshCurrentPage(); }); // Listen to leaves const u2 = FS.listen('leaves', docs => { LEAVES = docs; refreshCurrentPage(); updatePendingBadge(); }); unsubListeners = [u1, u2]; } function stopListeners() { unsubListeners.forEach(fn => fn()); unsubListeners = []; } // Refresh whatever page is currently active function refreshCurrentPage() { if (!CU) return; if (CU.role === 'admin') { const active = document.querySelector('.pg.active'); if (!active) return; const id = active.id; if (id === 'pg-ds') rDash(); if (id === 'pg-rq') rReqs(rf); if (id === 'pg-mb') rMembs(); if (id === 'pg-ac') refreshAdminCal(); updatePendingBadge(); } else { const active = document.querySelector('.pg.active'); if (!active) return; const id = active.id; if (id === 'pg-um') { rUSt(); rULL(); } if (id === 'pg-ur') { rUPend(); refreshUserReqCal(); } if (id === 'pg-uc') refreshUserTeamCal(); } } // ════════════════════════════════════ // AUTH // ════════════════════════════════════ async function doLogin() { const em = document.getElementById('lei').value.trim().toLowerCase(); const pw = document.getElementById('lpi').value; const btn = document.getElementById('login-btn'); const err = document.getElementById('lerr'); if (!em || !pw) { err.style.display='block'; err.textContent='⚠ Email aur password dono bharo!'; return; } btn.disabled = true; btn.textContent = 'Logging in...'; err.style.display = 'none'; try { // Refresh users from Firestore await loadUsers(); const user = USERS.find(u => u.email === em && u.password === pw); if (!user) { err.style.display = 'block'; err.innerHTML = '❌ Galat email ya password. Dobara try karo.'; document.getElementById('lpi').value = ''; btn.disabled = false; btn.textContent = 'Login Karo →'; return; } CU = user; localStorage.setItem('lms_session', JSON.stringify({ id: user.id })); btn.disabled = false; btn.textContent = 'Login Karo →'; mountApp(); } catch(e) { err.style.display = 'block'; err.textContent = '⚠ Login error: ' + e.message; btn.disabled = false; btn.textContent = 'Login Karo →'; } } function doLogout() { CU = null; localStorage.removeItem('lms_session'); stopListeners(); Object.values(cals).forEach(c => { try { c.destroy(); } catch {} }); cals = {}; hide('app'); show('lp','flex'); document.getElementById('lei').value = ''; document.getElementById('lpi').value = ''; } // ════════════════════════════════════ // MOUNT APP // ════════════════════════════════════ function mountApp() { hide('lp'); show('app','block'); document.getElementById('sbname').textContent = CU.name; document.getElementById('sbemail').textContent = CU.email; const av = document.getElementById('sbav'); av.textContent = ini(CU.name); av.style.background = CU.color || '#7c3aed'; const pill = document.getElementById('rpill'); pill.innerHTML = CU.role === 'admin' ? '⚡ Admin' : '👤 Member'; pill.className = 'rpill ' + CU.role; startListeners(); if (CU.role === 'admin') buildAdmin(); else buildUser(); } // ── STAT CARD ── function stCard(color, icon, val, lbl) { return `
${icon}
${val}
${lbl}
`; } // ════════════════════════════════════ // ADMIN DASHBOARD // ════════════════════════════════════ function buildAdmin() { document.getElementById('sbn').innerHTML = `
Menu
`; document.getElementById('mc').innerHTML = `

📊 Admin Dashboard

Real-time sync — sabhi devices par update hoga

⏳ Pending Requests

📋 Leave Requests

Sabhi requests approve ya reject karo

MemberChutti DateReasonRequest DateStatusAction

📅 Team Calendar

Kaun kab chutti par hai — ek nazar mein

👥 Team Members

Members add aur manage karo

`; rDash(); } function updatePendingBadge() { const pd = LEAVES.filter(l => l.status === 'pending'); const b = document.getElementById('pbd'); if (b) { b.textContent = pd.length; b.style.display = pd.length ? 'inline' : 'none'; } } function aP(p) { document.querySelectorAll('.pg').forEach(x => x.classList.remove('active')); document.querySelectorAll('.nav-item').forEach(x => x.classList.remove('active')); document.getElementById('pg-'+p).classList.add('active'); document.getElementById('n-'+p).classList.add('active'); if (p==='ds') rDash(); if (p==='rq') rReqs(rf); if (p==='ac') iACal(); if (p==='mb') rMembs(); } // ADMIN: Dashboard render function rDash() { const tm = USERS.filter(u => u.role === 'user'); const pd = LEAVES.filter(l => l.status === 'pending'); const ap = LEAVES.filter(l => l.status === 'approved'); const rj = LEAVES.filter(l => l.status === 'rejected'); updatePendingBadge(); const ast = document.getElementById('ast'); if (!ast) return; ast.innerHTML = stCard('#7c3aed','👥', tm.length, 'Team Members') + stCard('#f59e0b','⏳', pd.length, 'Pending Requests') + stCard('#10b981','✅', ap.length, 'Approved Leaves') + stCard('#ef4444','❌', rj.length, 'Rejected'); const apl = document.getElementById('apl'); if (!apl) return; if (!pd.length) { apl.innerHTML = '
🎉

Koi pending request nahi hai!

'; return; } const sorted = [...pd].sort((a,b) => new Date(a.date) - new Date(b.date)); apl.innerHTML = sorted.slice(0,6).map(l => `
${ini(l.uname)}
${l.uname}
📅 ${fmD(l.date)}${l.reason ? ' · ' + l.reason : ''}
`).join(''); } // ADMIN: Filter function aFlt(f, btn) { rf = f; document.querySelectorAll('.ftab').forEach(b => b.classList.remove('active')); btn.classList.add('active'); rReqs(f); } // ADMIN: Requests table function rReqs(f) { let ls = [...LEAVES]; if (f !== 'all') ls = ls.filter(l => l.status === f); ls.sort((a,b) => new Date(b.createdAt) - new Date(a.createdAt)); const tb = document.getElementById('rtb'); if (!tb) return; if (!ls.length) { tb.innerHTML = '
📭

Koi request nahi mili

'; return; } tb.innerHTML = ls.map(l => `
${ini(l.uname)}
${l.uname}
${fmD(l.date)} ${l.reason||'---'} ${fmS(l.createdAt)} ${sbdg(l.status)} ${l.status==='pending' ? `
` : '---'} `).join(''); } // ADMIN: Update leave status (Firestore) async function setSt(id, st) { try { await FS.update('leaves', id, { status: st, updatedAt: new Date().toISOString() }); // LEAVES cache updated via realtime listener toast(st==='approved' ? '✅ Leave approve kar di!' : '❌ Leave reject kar di!', st==='approved' ? 'success' : 'error'); } catch(e) { toast('⚠ Error: ' + e.message, 'error'); } } // ADMIN: Calendar events function apEvs() { return LEAVES .filter(l => l.status === 'approved') .map(l => ({ id:l.id, title:l.uname, date:l.date, backgroundColor:gCol(l.uid), borderColor:'transparent' })); } function refreshAdminCal() { if (cals.ac) { cals.ac.removeAllEvents(); cals.ac.addEventSource(apEvs()); bLgnd('alg'); } } function bLgnd(cid) { const el = document.getElementById(cid); if (!el) return; const ms = USERS.filter(u => u.role === 'user'); el.innerHTML = ms.map(m => `
${m.name}
`).join('') || 'Koi member nahi'; } function iACal() { bLgnd('alg'); if (cals.ac) return; raf(() => { const el = document.getElementById('acal'); if (!el) return; cals.ac = new FullCalendar.Calendar(el, { initialView: 'dayGridMonth', headerToolbar: { left:'prev,next today', center:'title', right:'dayGridMonth,dayGridWeek' }, height: 580, events: apEvs(), eventClick(info) { toast('😆 '+info.event.title+' — '+fmD(info.event.startStr), 'info'); } }); cals.ac.render(); }); } // ADMIN: Members list function rMembs() { const ms = USERS.filter(u => u.role === 'user'); const el = document.getElementById('mbl'); if (!el) return; if (!ms.length) { el.innerHTML = '
👥

Koi member nahi. "+ Member Add Karo" se add karo.

'; return; } el.innerHTML = ms.map(m => { const ac = LEAVES.filter(l => l.uid === m.id && l.status === 'approved').length; const pc = LEAVES.filter(l => l.uid === m.id && l.status === 'pending').length; return `
${ini(m.name)}
${m.name}
${m.email}
✅ ${ac} approved ${pc ? `⏳ ${pc} pending` : ''}
`; }).join(''); } // ADMIN: Open add member modal function oAddM() { openM('👤 Naya Member Add Karo', `

💡 Yeh credentials member ko share karo — woh isse login karenge

`); } // ADMIN: Add member (Firestore) async function addM() { const n = document.getElementById('nm').value.trim(); const e = document.getElementById('ne').value.trim().toLowerCase(); const p = document.getElementById('np').value; const btn = document.getElementById('add-btn'); if (!n||!e||!p) { toast('⚠ Sabhi fields bharo!','error'); return; } btn.disabled = true; btn.textContent = 'Adding...'; try { // Check email uniqueness const existing = USERS.find(u => u.email === e); if (existing) { toast('⚠ Yeh email pehle se registered hai!','error'); btn.disabled=false; btn.textContent='✅ Add Karo'; return; } const colorUsers = USERS.filter(u => u.role === 'user'); const ci = colorUsers.length % COLS.length; const newUser = { name: n, email: e, password: p, role: 'user', color: COLS[ci], createdAt: new Date().toISOString() }; await FS.add('users', newUser); // USERS will update via realtime listener closeM(); toast('✅ ' + n + ' ko team mein add kar diya!', 'success'); } catch(err) { toast('⚠ Error: ' + err.message, 'error'); btn.disabled = false; btn.textContent = '✅ Add Karo'; } } // ADMIN: Remove member (Firestore) async function rmM(id, name) { if (!confirm('Kya aap "'+name+'" ko remove karna chahte hain?\nUnke sabhi leave records bhi delete ho jayenge.')) return; try { // Delete user await FS.del('users', id); // Delete all their leaves const userLeaves = LEAVES.filter(l => l.uid === id); await Promise.all(userLeaves.map(l => FS.del('leaves', l.id))); // Cache updated via listeners toast('🗑 '+name+' ko remove kar diya!', 'info'); } catch(err) { toast('⚠ Error: ' + err.message, 'error'); } } // ════════════════════════════════════ // USER DASHBOARD // ════════════════════════════════════ function buildUser() { document.getElementById('sbn').innerHTML = `
Menu
`; document.getElementById('mc').innerHTML = `

🏖 Meri Chutti

Live — admin approve karte hi update hoga

📋 Leave History

📝 Chutti Mangao

Calendar mein kisi bhi future date par click karke request karo

📌 Aage ki kisi bhi date par click karo — chutti request karo!
⏳ Pending Requests

📅 Team Calendar

Dekho team mein kaun kab chutti par hai

`; rUSt(); rULL(); } function uP(p) { document.querySelectorAll('.pg').forEach(x => x.classList.remove('active')); document.querySelectorAll('.nav-item').forEach(x => x.classList.remove('active')); document.getElementById('pg-'+p).classList.add('active'); document.getElementById('n-'+p).classList.add('active'); if (p==='um') { rUSt(); rULL(); } if (p==='ur') iURC(); if (p==='uc') iUTC(); } // USER: Stats function rUSt() { const my = LEAVES.filter(l => l.uid === CU.id); const el = document.getElementById('ust'); if (!el) return; el.innerHTML = stCard('#7c3aed','📋', my.length, 'Total Requests') + stCard('#f59e0b','⏳', my.filter(l=>l.status==='pending').length, 'Pending') + stCard('#10b981','✅', my.filter(l=>l.status==='approved').length, 'Approved') + stCard('#ef4444','❌',my.filter(l=>l.status==='rejected').length, 'Rejected'); } // USER: Leave history function rULL() { const my = LEAVES.filter(l => l.uid === CU.id).sort((a,b) => new Date(b.createdAt) - new Date(a.createdAt)); const el = document.getElementById('ull'); if (!el) return; if (!my.length) { el.innerHTML = '
🌴

Abhi tak koi leave nahi ki.
"Chutti Mangao" se pehli request karo!

'; return; } el.innerHTML = my.map(l => `
📅 ${fmD(l.date)}
${l.reason || 'Koi reason nahi diya'}
${fmS(l.createdAt)} ko request ki
${sbdg(l.status)}
`).join(''); } // USER: Init request calendar function iURC() { rUPend(); if (cals.ur) return; raf(() => { const el = document.getElementById('urc'); if (!el) return; const myLeaves = LEAVES.filter(l => l.uid === CU.id); const bg = myLeaves.map(l => ({ date: l.date, display: 'background', backgroundColor: l.status==='approved' ? 'rgba(16,185,129,.25)' : l.status==='rejected' ? 'rgba(239,68,68,.18)' : 'rgba(245,158,11,.22)' })); cals.ur = new FullCalendar.Calendar(el, { initialView: 'dayGridMonth', headerToolbar: { left:'prev,next today', center:'title', right:'' }, height: 420, events: bg, dateClick(info) { const d = info.dateStr; if (d < tod()) { toast('⚠ Pichli date ke liye request nahi ho sakti!','error'); return; } const ex = LEAVES.find(l => l.uid===CU.id && l.date===d); if (ex) { toast('⚠ Is date ke liye pehle se request hai ('+ex.status+')','error'); return; } oLM(d); } }); cals.ur.render(); }); } function refreshUserReqCal() { if (cals.ur) { cals.ur.removeAllEvents(); const myLeaves = LEAVES.filter(l => l.uid === CU.id); const bg = myLeaves.map(l => ({ date: l.date, display: 'background', backgroundColor: l.status==='approved' ? 'rgba(16,185,129,.25)' : l.status==='rejected' ? 'rgba(239,68,68,.18)' : 'rgba(245,158,11,.22)' })); cals.ur.addEventSource(bg); } rUPend(); } // USER: Leave modal function oLM(d) { openM('📝 Chutti Ki Request', `
📅 Selected Date: ${fmD(d)}
`); } // USER: Submit leave (Firestore) async function subLv(d) { const reason = (document.getElementById('lvr')?.value || '').trim(); const btn = document.getElementById('sub-btn'); if (btn) { btn.disabled=true; btn.textContent='Submitting...'; } try { await FS.add('leaves', { uid: CU.id, uname: CU.name, date: d, reason, status: 'pending', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }); closeM(); toast('✅ Chutti ki request bhej di! Admin approve karega.', 'success'); // Cache updated via realtime listener // Rebuild calendar to show new highlight if (cals.ur) { cals.ur.destroy(); delete cals.ur; } iURC(); rUSt(); rULL(); } catch(e) { toast('⚠ Error: ' + e.message, 'error'); if (btn) { btn.disabled=false; btn.textContent='📤 Request Bhejo'; } } } // USER: Pending list function rUPend() { const el = document.getElementById('upl'); if (!el) return; const pd = LEAVES.filter(l => l.uid===CU.id && l.status==='pending') .sort((a,b) => new Date(a.date) - new Date(b.date)); if (!pd.length) { el.innerHTML='

Koi pending request nahi hai

'; return; } el.innerHTML = pd.map(l => `
📅 ${fmD(l.date)}
${l.reason || 'Koi reason nahi'}
⏳ Pending
`).join(''); } // USER: Cancel leave (Firestore) async function canLv(id) { if (!confirm('Kya aap yeh leave request cancel karna chahte hain?')) return; try { await FS.del('leaves', id); toast('🗑 Leave request cancel kar di!', 'info'); if (cals.ur) { cals.ur.destroy(); delete cals.ur; } iURC(); rUSt(); rULL(); } catch(e) { toast('⚠ Error: ' + e.message, 'error'); } } // USER: Team calendar function iUTC() { bLgnd('ulg'); if (cals.uc) return; raf(() => { const el = document.getElementById('utc'); if (!el) return; cals.uc = new FullCalendar.Calendar(el, { initialView: 'dayGridMonth', headerToolbar: { left:'prev,next today', center:'title', right:'dayGridMonth,dayGridWeek' }, height: 580, events: apEvs(), eventClick(info) { toast('😆 '+info.event.title+' — '+fmD(info.event.startStr), 'info'); } }); cals.uc.render(); }); } function refreshUserTeamCal() { if (cals.uc) { cals.uc.removeAllEvents(); cals.uc.addEventSource(apEvs()); bLgnd('ulg'); } } // ── KEYBOARD ── document.addEventListener('keydown', e => { if (e.key==='Enter' && document.getElementById('lp').style.display!=='none') doLogin(); }); // ── BOOT ── boot();