0x1998 - MANAGER
Dรผzenlenen Dosya: clients.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Clients โ 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"> <!-- Sidebar --> <aside class="sidebar"> <h2 class="logo">Nexlance</h2> <ul class="nav"> <li><a href="dashboard.html">๐ Dashboard</a></li> <li class="active"><a href="clients.html">๐ฅ Clients</a></li> <li><a href="team.html">๐งโ๐ผ Team</a></li> <li><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 --> <main class="main"> <div class="topbar"> <input type="text" id="globalSearch" placeholder="Search clients, projects, invoices..."> <div class="profile"><a href="admin.html" style="color:inherit;text-decoration:none;">โ๏ธ Admin</a></div> </div> <!-- Page Header --> <div class="page-header"> <div class="page-header-left"> <h1>Clients</h1> <p>Manage all your client relationships</p> </div> <div class="page-header-actions"> <button class="btn btn-primary" onclick="openAddModal()">+ Add Client</button> </div> </div> <!-- Stats --> <div class="stats-grid"> <div class="stat-card"> <div class="stat-label">Total Clients</div> <div class="stat-value" id="statTotal">0</div> <div class="stat-sub">All time</div> </div> <div class="stat-card green"> <div class="stat-label">Active Projects</div> <div class="stat-value" id="statActive">0</div> <div class="stat-sub">In progress</div> </div> <div class="stat-card orange"> <div class="stat-label">Pending Payments</div> <div class="stat-value" id="statPending">โน0</div> <div class="stat-sub">Unpaid balance</div> </div> <div class="stat-card blue"> <div class="stat-label">Total Revenue</div> <div class="stat-value" id="statRevenue">โน0</div> <div class="stat-sub">Contract value</div> </div> </div> <!-- Filter Bar --> <div class="filter-bar"> <input type="search" class="search-input" id="searchInput" placeholder="๐ Search by name, company, domain..."> <select id="filterType"> <option value="">All Website Types</option> <option>Business Website</option> <option>Ecommerce Website</option> <option>Landing Page</option> <option>Website Redesign</option> </select> <select id="filterPlan"> <option value="">All Plans</option> <option>Basic</option> <option>Premium</option> <option>Custom</option> </select> <div class="spacer"></div> <span id="clientCount" style="font-size:0.82rem;color:#aaa;"></span> </div> <!-- Table --> <div class="table-card"> <div class="table-header"> <h3>Client List</h3> <span id="tableCount"></span> </div> <table> <thead> <tr> <th>Client</th> <th>Website Type</th> <th>Plan</th> <th>Platform</th> <th>Last Payment</th> <th>Total Value</th> <th>Balance Due</th> <th>Actions</th> </tr> </thead> <tbody id="clientsTableBody"> <tr><td colspan="8"><div class="empty-state"><div class="e-icon">โณ</div><h3>Loading clients...</h3></div></td></tr> </tbody> </table> </div> </main> </div> <!-- Add / Edit Client Modal --> <div class="modal-overlay" id="clientModal"> <div class="modal modal-lg"> <div class="modal-header"> <h2 id="modalTitle">Add New Client</h2> <button class="modal-close" onclick="closeModal()">โ</button> </div> <h4 style="font-size:0.82rem;color:#6c5ce7;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:14px;">Basic Information</h4> <div class="form-grid"> <div class="form-group"> <label>Full Name *</label> <input type="text" id="fName" placeholder="Rahul Sharma"> </div> <div class="form-group"> <label>Email Address</label> <input type="email" id="fEmail" placeholder="client@company.com"> </div> <div class="form-group"> <label>Phone Number</label> <input type="tel" id="fPhone" placeholder="+91 98765 43210"> </div> <div class="form-group"> <label>Company Name</label> <input type="text" id="fCompany" placeholder="Company Pvt Ltd"> </div> <div class="form-group"> <label>Domain Name</label> <input type="text" id="fDomain" placeholder="company.in"> </div> <div class="form-group"> <label>Hosting Provider</label> <input type="text" id="fHostingProvider" placeholder="Hostinger / AWS / GoDaddy"> </div> </div> <h4 style="font-size:0.82rem;color:#6c5ce7;text-transform:uppercase;letter-spacing:0.5px;margin:18px 0 14px;">Website Details</h4> <div class="form-grid"> <div class="form-group"> <label>Project Type</label> <select id="fProjectType"> <option value="">Select type</option> <option>Business Website</option> <option>Ecommerce Website</option> <option>Landing Page</option> <option>Website Redesign</option> </select> </div> <div class="form-group"> <label>Platform</label> <select id="fPlatform"> <option value="">Select platform</option> <option>WordPress</option> <option>Shopify</option> <option>WooCommerce</option> <option>Custom</option> <option>Webflow</option> </select> </div> <div class="form-group"> <label>Hosting Expiry</label> <input type="date" id="fHostingExpiry"> </div> <div class="form-group"> <label>SSL Expiry</label> <input type="date" id="fSslExpiry"> </div> <div class="form-group"> <label>Maintenance Plan</label> <select id="fMaintenance"> <option>None</option> <option>Monthly</option> <option>Quarterly</option> <option>Annual</option> </select> </div> <div class="form-group"> <label>Plan Type</label> <select id="fPlanType"> <option>Basic</option> <option>Premium</option> <option>Custom</option> </select> </div> </div> <h4 style="font-size:0.82rem;color:#6c5ce7;text-transform:uppercase;letter-spacing:0.5px;margin:18px 0 14px;">Billing</h4> <div class="form-grid"> <div class="form-group"> <label>Total Contract Value (โน)</label> <input type="number" id="fContractValue" placeholder="45000"> </div> <div class="form-group"> <label>Paid Amount (โน)</label> <input type="number" id="fPaidAmount" placeholder="35000"> </div> </div> <div class="modal-footer"> <button class="btn btn-secondary" onclick="closeModal()">Cancel</button> <button class="btn btn-primary" onclick="saveClient()">Save Client</button> </div> </div> </div> <script src="supabase-config.js"></script> <script> let clients = []; let editingId = null; async function init() { clients = await fetchClients(); renderStats(); renderTable(clients); } function renderStats() { const total = clients.length; const pending = clients.reduce((s, c) => s + (c.total_contract_value - c.paid_amount), 0); const revenue = clients.reduce((s, c) => s + (c.total_contract_value || 0), 0); document.getElementById('statTotal').textContent = total; document.getElementById('statActive').textContent = clients.filter(c => (c.total_contract_value - c.paid_amount) > 0).length; document.getElementById('statPending').textContent = formatCurrency(pending); document.getElementById('statRevenue').textContent = formatCurrency(revenue); } function statusBadge(type) { const map = { 'Business Website': 'badge-blue', 'Ecommerce Website': 'badge-purple', 'Landing Page': 'badge-teal', 'Website Redesign': 'badge-orange' }; return `<span class="badge ${map[type] || 'badge-gray'}">${type || 'โ'}</span>`; } function planBadge(plan) { const map = { 'Premium': 'badge-purple', 'Basic': 'badge-blue', 'Custom': 'badge-orange' }; return `<span class="badge ${map[plan] || 'badge-gray'}">${plan || 'โ'}</span>`; } function renderTable(data) { const tbody = document.getElementById('clientsTableBody'); document.getElementById('tableCount').textContent = data.length + ' clients'; if (!data.length) { tbody.innerHTML = `<tr><td colspan="8"><div class="empty-state"><div class="e-icon">๐ฅ</div><h3>No clients found</h3><p>Add your first client to get started</p></div></td></tr>`; return; } tbody.innerHTML = data.map(c => { const balance = (c.total_contract_value || 0) - (c.paid_amount || 0); return `<tr> <td data-label="Client"> <div style="font-weight:600;color:#333;">${c.name}</div> <div style="font-size:0.78rem;color:#aaa;">${c.company || ''}</div> </td> <td data-label="Type">${statusBadge(c.project_type)}</td> <td data-label="Plan">${planBadge(c.plan_type)}</td> <td data-label="Platform"><span style="color:#666;">${c.platform || 'โ'}</span></td> <td data-label="Last Payment"><span style="color:#666;font-size:0.85rem;">${c.paid_amount ? formatCurrency(c.paid_amount) : 'โ'}</span></td> <td data-label="Total Value"><strong style="color:#4b3fbf;">${formatCurrency(c.total_contract_value || 0)}</strong></td> <td data-label="Balance Due"> <span class="${balance > 0 ? 'badge badge-red' : 'badge badge-green'}">${balance > 0 ? formatCurrency(balance) : 'Paid โ'}</span> </td> <td data-label="Actions"> <div class="table-actions"> <a href="client-detail.html?id=${c.id}" class="action-btn action-view" title="View Profile">๐</a> <button class="action-btn action-edit" onclick="openEditModal('${c.id}')" title="Edit">โ๏ธ</button> <button class="action-btn action-delete" onclick="handleDelete('${c.id}')" title="Delete">๐</button> </div> </td> </tr>`; }).join(''); } function filterClients() { const search = document.getElementById('searchInput').value.toLowerCase(); const type = document.getElementById('filterType').value; const plan = document.getElementById('filterPlan').value; const filtered = clients.filter(c => { const matchSearch = !search || (c.name || '').toLowerCase().includes(search) || (c.company || '').toLowerCase().includes(search) || (c.domain_name || '').toLowerCase().includes(search) || (c.email || '').toLowerCase().includes(search); const matchType = !type || c.project_type === type; const matchPlan = !plan || c.plan_type === plan; return matchSearch && matchType && matchPlan; }); renderTable(filtered); } document.getElementById('searchInput').addEventListener('input', filterClients); document.getElementById('filterType').addEventListener('change', filterClients); document.getElementById('filterPlan').addEventListener('change', filterClients); function openAddModal() { editingId = null; document.getElementById('modalTitle').textContent = 'Add New Client'; ['fName','fEmail','fPhone','fCompany','fDomain','fHostingProvider','fHostingExpiry','fSslExpiry','fContractValue','fPaidAmount'].forEach(id => document.getElementById(id).value = ''); document.getElementById('fProjectType').value = ''; document.getElementById('fPlatform').value = ''; document.getElementById('fMaintenance').value = 'None'; document.getElementById('fPlanType').value = 'Basic'; document.getElementById('clientModal').classList.add('active'); } function openEditModal(id) { const c = clients.find(x => x.id === id); if (!c) return; editingId = id; document.getElementById('modalTitle').textContent = 'Edit Client'; document.getElementById('fName').value = c.name || ''; document.getElementById('fEmail').value = c.email || ''; document.getElementById('fPhone').value = c.phone || ''; document.getElementById('fCompany').value = c.company || ''; document.getElementById('fDomain').value = c.domain_name || ''; document.getElementById('fHostingProvider').value = c.hosting_provider || ''; document.getElementById('fProjectType').value = c.project_type || ''; document.getElementById('fPlatform').value = c.platform || ''; document.getElementById('fHostingExpiry').value = c.hosting_expiry || ''; document.getElementById('fSslExpiry').value = c.ssl_expiry || ''; document.getElementById('fMaintenance').value = c.maintenance_plan || 'None'; document.getElementById('fPlanType').value = c.plan_type || 'Basic'; document.getElementById('fContractValue').value = c.total_contract_value || ''; document.getElementById('fPaidAmount').value = c.paid_amount || ''; document.getElementById('clientModal').classList.add('active'); } function closeModal() { document.getElementById('clientModal').classList.remove('active'); } async function saveClient() { const name = document.getElementById('fName').value.trim(); if (!name) { showToast('Client name is required', 'error'); return; } const he = document.getElementById('fHostingExpiry').value; const se = document.getElementById('fSslExpiry').value; if (he && !isValidDate(he)) { markDateError('fHostingExpiry', 'Invalid hosting expiry date'); showToast('Enter a valid hosting expiry date', 'error'); return; } if (se && !isValidDate(se)) { markDateError('fSslExpiry', 'Invalid SSL expiry date'); showToast('Enter a valid SSL expiry date', 'error'); return; } clearDateError('fHostingExpiry'); clearDateError('fSslExpiry'); const data = { name, email: document.getElementById('fEmail').value.trim(), phone: document.getElementById('fPhone').value.trim(), company: document.getElementById('fCompany').value.trim(), domain_name: document.getElementById('fDomain').value.trim(), hosting_provider: document.getElementById('fHostingProvider').value.trim(), project_type: document.getElementById('fProjectType').value, platform: document.getElementById('fPlatform').value, hosting_expiry: he || null, ssl_expiry: se || null, maintenance_plan: document.getElementById('fMaintenance').value, plan_type: document.getElementById('fPlanType').value, total_contract_value: Number(document.getElementById('fContractValue').value) || 0, paid_amount: Number(document.getElementById('fPaidAmount').value) || 0 }; try { if (editingId) { const updated = await updateClient(editingId, data); const idx = clients.findIndex(c => c.id === editingId); if (idx > -1) clients[idx] = updated; showToast('Client updated successfully!', 'success'); } else { const created = await addClient(data); clients.unshift(created); showToast('Client added successfully!', 'success'); } closeModal(); renderStats(); filterClients(); } catch (e) { showToast('Error: ' + e.message, 'error'); } } async function handleDelete(id) { if (!confirm('Delete this client? This action cannot be undone.')) return; try { await deleteClient(id); clients = clients.filter(c => c.id !== id); renderStats(); filterClients(); showToast('Client deleted.', 'info'); } catch (e) { showToast('Error: ' + e.message, 'error'); } } // Close modal on overlay click document.getElementById('clientModal').addEventListener('click', function(e) { if (e.target === this) closeModal(); }); attachDateValidation('fHostingExpiry', { label: 'Hosting Expiry' }); attachDateValidation('fSslExpiry', { label: 'SSL Expiry' }); init(); </script> </body> </html>
geri dรถn