⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.235
Server IP:
162.0.217.164
Server:
Linux premium256.web-hosting.com 4.18.0-553.54.1.lve.el8.x86_64 #1 SMP Wed Jun 4 13:01:13 UTC 2025 x86_64
Server Software:
LiteSpeed
PHP Version:
8.0.30
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
home
/
niyknzcu
/
nexlancedigital.com
/
View File Name :
projects.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Projects โ Nexlance</title> <link rel="stylesheet" href="dashboard.css"> <link rel="stylesheet" href="app.css"> <!-- Firebase SDK --> <script src="https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js"></script> <script src="https://www.gstatic.com/firebasejs/10.14.1/firebase-auth-compat.js"></script> <script src="https://www.gstatic.com/firebasejs/10.14.1/firebase-firestore-compat.js"></script> </head> <body> <div class="container"> <aside class="sidebar"> <h2 class="logo">Nexlance</h2> <ul class="nav"> <li><a href="dashboard.html">๐ Dashboard</a></li> <li><a href="clients.html">๐ฅ Clients</a></li> <li><a href="team.html">๐งโ๐ผ Team</a></li> <li class="active"><a href="projects.html">๐ Projects</a></li> <li><a href="invoices.html">๐งพ Invoices</a></li> <li><a href="services.html">๐ Services</a></li> <li><a href="access-roles.html">๐ Access / Roles</a></li> <hr style="border:none;border-top:1px solid #c9bfff;margin:12px 0 8px;"> <li><a href="developer-info.html">๐จโ๐ป Support Info</a></li> </ul> </aside> <main class="main"> <div class="topbar"> <input type="text" placeholder="Search projects..."> <div class="profile"><a href="admin.html" style="color:inherit;text-decoration:none;">โ๏ธ Admin</a></div> </div> <div class="page-header"> <div class="page-header-left"> <h1>Projects</h1> <p>Track all website projects and their progress</p> </div> <div class="page-header-actions"> <button class="btn btn-primary" onclick="openAddModal()">+ New Project</button> </div> </div> <!-- Stats --> <div class="stats-grid"> <div class="stat-card"><div class="stat-label">Total Projects</div><div class="stat-value" id="sTotal">0</div></div> <div class="stat-card blue"><div class="stat-label">In Progress</div><div class="stat-value" id="sActive">0</div></div> <div class="stat-card green"><div class="stat-label">Live / Completed</div><div class="stat-value" id="sDone">0</div></div> <div class="stat-card red"><div class="stat-label">On Hold / Delayed</div><div class="stat-value" id="sHold">0</div></div> <div class="stat-card orange"><div class="stat-label">Pending Review</div><div class="stat-value" id="sReview">0</div></div> </div> <!-- Filter Bar --> <div class="filter-bar"> <input type="search" class="search-input" id="searchInput" placeholder="๐ Search projects or clients..."> <select id="filterStatus"> <option value="">All Statuses</option> <option>Planning</option> <option>Design</option> <option>Development</option> <option>Testing</option> <option>Live</option> <option>On Hold</option> </select> <div class="spacer"></div> </div> <!-- Projects Table --> <div class="table-card"> <div class="table-header"> <h3>All Projects</h3> <span id="tableCount"></span> </div> <table> <thead> <tr> <th>Project Name</th> <th>Client</th> <th>Start Date</th> <th>Deadline</th> <th>Status</th> <th>Assigned Team</th> <th>Progress</th> <th>Actions</th> </tr> </thead> <tbody id="projectsBody"> <tr><td colspan="8"><div class="empty-state"><div class="e-icon">โณ</div><h3>Loading projects...</h3></div></td></tr> </tbody> </table> </div> </main> </div> <!-- Add / Edit Project Modal --> <div class="modal-overlay" id="projectModal"> <div class="modal modal-lg"> <div class="modal-header"> <h2 id="modalTitle">New Project</h2> <button class="modal-close" onclick="closeModal()">โ</button> </div> <div class="form-grid"> <div class="form-group full"><label>Project Name *</label><input type="text" id="fName" placeholder="Client โ Website Project"></div> <div class="form-group"><label>Client Name</label><input type="text" id="fClientName" placeholder="Company name"></div> <div class="form-group"><label>Status</label> <select id="fStatus"><option>Planning</option><option>Design</option><option>Development</option><option>Testing</option><option>Live</option><option>On Hold</option></select> </div> <div class="form-group"><label>Start Date</label><input type="date" id="fStart"></div> <div class="form-group"><label>Deadline</label><input type="date" id="fDeadline"></div> <div class="form-group"><label>Assigned Team</label><input type="text" id="fTeam" placeholder="Arjun, Priya"></div> <div class="form-group"><label>Progress (%)</label><input type="number" id="fProgress" min="0" max="100" placeholder="0"></div> <div class="form-group full"><label>Scope of Work</label><textarea id="fScope" placeholder="Describe the scope of this project..."></textarea></div> <div class="form-group full"><label>Deliverables</label><textarea id="fDeliverables" placeholder="List the deliverables..."></textarea></div> </div> <div class="modal-footer"> <button class="btn btn-secondary" onclick="closeModal()">Cancel</button> <button class="btn btn-primary" onclick="saveProject()">Save Project</button> </div> </div> </div> <script src="supabase-config.js"></script> <script> let projects = []; let editingId = null; const statusBadges = { 'Live':'badge-green','Development':'badge-blue','Design':'badge-purple', 'Testing':'badge-teal','Planning':'badge-gray','On Hold':'badge-orange' }; async function init() { projects = await fetchProjects(); renderStats(); renderTable(projects); } function renderStats() { document.getElementById('sTotal').textContent = projects.length; document.getElementById('sActive').textContent = projects.filter(p => ['Development','Design','Testing'].includes(p.status)).length; document.getElementById('sDone').textContent = projects.filter(p => p.status === 'Live').length; document.getElementById('sHold').textContent = projects.filter(p => p.status === 'On Hold').length; document.getElementById('sReview').textContent = projects.filter(p => p.status === 'Planning').length; } function renderTable(data) { const tbody = document.getElementById('projectsBody'); document.getElementById('tableCount').textContent = data.length + ' projects'; if (!data.length) { tbody.innerHTML = `<tr><td colspan="8"><div class="empty-state"><div class="e-icon">๐</div><h3>No projects found</h3><p>Create your first project</p></div></td></tr>`; return; } const today = new Date(); tbody.innerHTML = data.map(p => { const deadline = p.deadline ? new Date(p.deadline) : null; const overdue = deadline && deadline < today && p.status !== 'Live'; return `<tr> <td><div style="font-weight:600;color:#333;">${p.name}</div></td> <td><div style="font-size:0.85rem;color:#666;">${p.client_name || 'โ'}</div></td> <td style="color:#888;font-size:0.85rem;">${formatDate(p.start_date)}</td> <td> <span style="color:${overdue ? '#d63031' : '#555'};font-size:0.85rem;font-weight:${overdue?'600':'400'}"> ${formatDate(p.deadline)}${overdue ? ' โ ๏ธ' : ''} </span> </td> <td><span class="badge ${statusBadges[p.status] || 'badge-gray'}">${p.status}</span></td> <td style="color:#666;font-size:0.85rem;">${p.assigned_team || 'โ'}</td> <td> <div class="progress-wrap"> <div class="progress-bar"><div class="progress-fill" style="width:${p.progress||0}%"></div></div> <div class="progress-text">${p.progress||0}%</div> </div> </td> <td> <div class="table-actions"> <a href="project-detail.html?id=${p.id}" class="action-btn action-view" title="View">๐</a> <button class="action-btn action-edit" onclick="openEditModal('${p.id}')" title="Edit">โ๏ธ</button> <button class="action-btn action-delete" onclick="handleDelete('${p.id}')" title="Delete">๐</button> </div> </td> </tr>`; }).join(''); } function filterProjects() { const search = document.getElementById('searchInput').value.toLowerCase(); const status = document.getElementById('filterStatus').value; const filtered = projects.filter(p => (!search || (p.name||'').toLowerCase().includes(search) || (p.client_name||'').toLowerCase().includes(search)) && (!status || p.status === status) ); renderTable(filtered); } document.getElementById('searchInput').addEventListener('input', filterProjects); document.getElementById('filterStatus').addEventListener('change', filterProjects); function openAddModal() { editingId = null; document.getElementById('modalTitle').textContent = 'New Project'; ['fName','fClientName','fStart','fDeadline','fTeam','fScope','fDeliverables'].forEach(id => document.getElementById(id).value = ''); document.getElementById('fStatus').value = 'Planning'; document.getElementById('fProgress').value = '0'; document.getElementById('projectModal').classList.add('active'); } function openEditModal(id) { const p = projects.find(x => x.id === id); if (!p) return; editingId = id; document.getElementById('modalTitle').textContent = 'Edit Project'; document.getElementById('fName').value = p.name || ''; document.getElementById('fClientName').value = p.client_name || ''; document.getElementById('fStatus').value = p.status || 'Planning'; document.getElementById('fStart').value = p.start_date || ''; document.getElementById('fDeadline').value = p.deadline || ''; document.getElementById('fTeam').value = p.assigned_team || ''; document.getElementById('fProgress').value = p.progress || 0; document.getElementById('fScope').value = p.scope_of_work || ''; document.getElementById('fDeliverables').value = p.deliverables || ''; document.getElementById('projectModal').classList.add('active'); } function closeModal() { document.getElementById('projectModal').classList.remove('active'); } async function saveProject() { const name = document.getElementById('fName').value.trim(); if (!name) { showToast('Project name is required', 'error'); return; } const start = document.getElementById('fStart').value; const deadline = document.getElementById('fDeadline').value; if (start && !isValidDate(start)) { markDateError('fStart', 'Invalid start date'); showToast('Enter a valid start date', 'error'); return; } if (deadline && !isValidDate(deadline)) { markDateError('fDeadline', 'Invalid deadline'); showToast('Enter a valid deadline', 'error'); return; } if (start && deadline && !isDateSameOrAfter(start, deadline)) { markDateError('fDeadline', 'Deadline must be on or after start date'); showToast('Deadline must be on or after the start date', 'error'); return; } clearDateError('fStart'); clearDateError('fDeadline'); const data = { name, client_name: document.getElementById('fClientName').value.trim(), status: document.getElementById('fStatus').value, start_date: start || null, deadline: deadline || null, assigned_team: document.getElementById('fTeam').value.trim(), progress: Number(document.getElementById('fProgress').value) || 0, scope_of_work: document.getElementById('fScope').value.trim(), deliverables: document.getElementById('fDeliverables').value.trim() }; try { if (editingId) { const upd = await updateProject(editingId, data); const idx = projects.findIndex(p => p.id === editingId); if (idx > -1) projects[idx] = upd; showToast('Project updated!', 'success'); } else { const created = await addProject(data); projects.unshift(created); showToast('Project created!', 'success'); } closeModal(); renderStats(); filterProjects(); } catch(e) { showToast('Error: ' + e.message, 'error'); } } async function handleDelete(id) { if (!confirm('Delete this project and all its tasks?')) return; try { await deleteProject(id); projects = projects.filter(p => p.id !== id); renderStats(); filterProjects(); showToast('Project deleted.', 'info'); } catch(e) { showToast('Error: ' + e.message, 'error'); } } document.getElementById('projectModal').addEventListener('click', function(e){ if(e.target===this) closeModal(); }); attachDateValidation('fStart', { label: 'Start Date' }); attachDateValidation('fDeadline', { label: 'Deadline' }); init(); </script> </body> </html>