Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Customer Database</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | |
| background-color: #f2f2f7; | |
| -webkit-font-smoothing: antialiased; | |
| } | |
| .frosted-glass { | |
| background: rgba(255, 255, 255, 0.7); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.5); | |
| } | |
| .customer-card { | |
| transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); | |
| border-radius: 12px; | |
| box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03); | |
| } | |
| .customer-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); | |
| } | |
| .status-badge { | |
| border-radius: 10px; | |
| font-size: 13px; | |
| font-weight: 500; | |
| padding: 3px 10px; | |
| } | |
| .segmented-control { | |
| display: flex; | |
| border-radius: 10px; | |
| background-color: rgba(0, 0, 0, 0.05); | |
| padding: 4px; | |
| } | |
| .segmented-control input[type="radio"] { | |
| display: none; | |
| } | |
| .segmented-control label { | |
| flex: 1; | |
| text-align: center; | |
| padding: 6px 12px; | |
| border-radius: 8px; | |
| font-size: 14px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| } | |
| .segmented-control input[type="radio"]:checked + label { | |
| background-color: white; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); | |
| } | |
| .action-button { | |
| transition: all 0.2s ease; | |
| border-radius: 10px; | |
| } | |
| .action-button:hover { | |
| background-color: rgba(0, 0, 0, 0.05); | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.3s ease-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .modal-overlay { | |
| background: rgba(0, 0, 0, 0.4); | |
| backdrop-filter: blur(8px); | |
| -webkit-backdrop-filter: blur(8px); | |
| } | |
| .modal-content { | |
| border-radius: 14px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); | |
| } | |
| .text-field { | |
| border-radius: 10px; | |
| background-color: rgba(0, 0, 0, 0.02); | |
| border: 1px solid rgba(0, 0, 0, 0.05); | |
| } | |
| .text-field:focus { | |
| background-color: white; | |
| border-color: rgba(0, 122, 255, 0.3); | |
| box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1); | |
| } | |
| .search-bar { | |
| background-color: rgba(0, 0, 0, 0.03); | |
| border-radius: 10px; | |
| transition: all 0.2s ease; | |
| } | |
| .search-bar:focus-within { | |
| background-color: white; | |
| box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1); | |
| } | |
| .empty-state { | |
| background: linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(245,245,245,0.9) 100%); | |
| border-radius: 14px; | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen"> | |
| <!-- Header --> | |
| <header class="frosted-glass fixed top-0 left-0 right-0 z-20"> | |
| <div class="container mx-auto px-4 py-4"> | |
| <div class="flex items-center justify-between"> | |
| <div> | |
| <h1 class="text-2xl font-bold text-gray-900">Customers</h1> | |
| <p class="text-sm text-gray-500 mt-1">Manage your customer database</p> | |
| </div> | |
| <button id="addCustomerBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-full flex items-center justify-center gap-2 transition-colors shadow-sm"> | |
| <i class="fas fa-plus"></i> | |
| <span class="text-sm font-medium">New</span> | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="container mx-auto px-4 pt-24 pb-8"> | |
| <!-- Filters and Search --> | |
| <div class="mb-6"> | |
| <div class="flex flex-col md:flex-row gap-4"> | |
| <div class="flex-1"> | |
| <div class="search-bar flex items-center px-3 py-2"> | |
| <i class="fas fa-search text-gray-400 mr-2"></i> | |
| <input type="text" id="searchInput" placeholder="Search customers..." | |
| class="w-full bg-transparent outline-none text-gray-700 placeholder-gray-400"> | |
| </div> | |
| </div> | |
| <div class="w-full md:w-64"> | |
| <div class="segmented-control"> | |
| <input type="radio" id="filter-all" name="status-filter" value="all" checked> | |
| <label for="filter-all" class="text-gray-700">All</label> | |
| <input type="radio" id="filter-active" name="status-filter" value="Active"> | |
| <label for="filter-active" class="text-gray-700">Active</label> | |
| <input type="radio" id="filter-inactive" name="status-filter" value="Inactive"> | |
| <label for="filter-inactive" class="text-gray-700">Inactive</label> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Customer List --> | |
| <div id="customerList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> | |
| <!-- Customer cards will be inserted here by JavaScript --> | |
| </div> | |
| <!-- Empty State --> | |
| <div id="emptyState" class="empty-state hidden p-8 text-center rounded-xl shadow-sm"> | |
| <div class="mx-auto w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4"> | |
| <i class="fas fa-user-friends text-gray-400 text-xl"></i> | |
| </div> | |
| <h3 class="text-lg font-medium text-gray-800 mb-1">No customers found</h3> | |
| <p class="text-gray-500 mb-4">Try adjusting your search or filter criteria</p> | |
| <button id="resetFiltersBtn" class="text-blue-500 font-medium text-sm">Reset all filters</button> | |
| </div> | |
| <!-- Pagination --> | |
| <div class="flex justify-center mt-8"> | |
| <div class="flex items-center gap-2"> | |
| <button id="prevPage" class="action-button w-10 h-10 flex items-center justify-center text-gray-500 disabled:opacity-40"> | |
| <i class="fas fa-chevron-left"></i> | |
| </button> | |
| <div class="flex gap-1"> | |
| <!-- Page numbers will be inserted here by JavaScript --> | |
| </div> | |
| <button id="nextPage" class="action-button w-10 h-10 flex items-center justify-center text-gray-500 disabled:opacity-40"> | |
| <i class="fas fa-chevron-right"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Add/Edit Customer Modal --> | |
| <div id="customerModal" class="fixed inset-0 z-50 hidden overflow-y-auto"> | |
| <div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | |
| <div class="fixed inset-0 transition-opacity modal-overlay" aria-hidden="true"></div> | |
| <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span> | |
| <div class="inline-block align-bottom bg-white rounded-xl text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full modal-content"> | |
| <div class="bg-white px-6 pt-6 pb-4"> | |
| <div class="flex justify-between items-start"> | |
| <h3 id="modalTitle" class="text-xl leading-6 font-bold text-gray-900">New Customer</h3> | |
| <button id="closeModal" class="text-gray-400 hover:text-gray-500 rounded-full w-8 h-8 flex items-center justify-center action-button"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="mt-6"> | |
| <form id="customerForm"> | |
| <input type="hidden" id="customerId"> | |
| <div class="mb-4"> | |
| <label for="name" class="block text-sm font-medium text-gray-700 mb-2">Full Name</label> | |
| <input type="text" id="name" name="name" class="text-field w-full px-4 py-3 focus:outline-none"> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="email" class="block text-sm font-medium text-gray-700 mb-2">Email</label> | |
| <input type="email" id="email" name="email" class="text-field w-full px-4 py-3 focus:outline-none"> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="phone" class="block text-sm font-medium text-gray-700 mb-2">Phone</label> | |
| <input type="tel" id="phone" name="phone" class="text-field w-full px-4 py-3 focus:outline-none"> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="status" class="block text-sm font-medium text-gray-700 mb-2">Status</label> | |
| <select id="status" name="status" class="text-field w-full px-4 py-3 focus:outline-none"> | |
| <option value="Active">Active</option> | |
| <option value="Inactive">Inactive</option> | |
| <option value="Pending">Pending</option> | |
| <option value="Banned">Banned</option> | |
| </select> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="joinDate" class="block text-sm font-medium text-gray-700 mb-2">Join Date</label> | |
| <input type="date" id="joinDate" name="joinDate" class="text-field w-full px-4 py-3 focus:outline-none"> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <div class="bg-gray-50 px-6 py-4 flex flex-row-reverse gap-3"> | |
| <button id="saveCustomer" type="button" class="w-full inline-flex justify-center rounded-xl border border-transparent shadow-sm px-6 py-3 bg-blue-500 text-base font-medium text-white hover:bg-blue-600 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm transition-colors"> | |
| Save | |
| </button> | |
| <button id="cancelModal" type="button" class="w-full inline-flex justify-center rounded-xl border border-gray-300 shadow-sm px-6 py-3 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm transition-colors"> | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Confirmation Modal --> | |
| <div id="confirmModal" class="fixed inset-0 z-50 hidden overflow-y-auto"> | |
| <div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | |
| <div class="fixed inset-0 transition-opacity modal-overlay" aria-hidden="true"></div> | |
| <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span> | |
| <div class="inline-block align-bottom bg-white rounded-xl text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full modal-content"> | |
| <div class="bg-white px-6 pt-6 pb-4"> | |
| <div class="sm:flex sm:items-start"> | |
| <div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10"> | |
| <i class="fas fa-exclamation text-red-500"></i> | |
| </div> | |
| <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | |
| <h3 class="text-lg leading-6 font-bold text-gray-900">Delete Customer</h3> | |
| <div class="mt-2"> | |
| <p class="text-sm text-gray-500">Are you sure you want to delete this customer? This action cannot be undone.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-50 px-6 py-4 flex flex-row-reverse gap-3"> | |
| <button id="confirmDelete" type="button" class="w-full inline-flex justify-center rounded-xl border border-transparent shadow-sm px-6 py-3 bg-red-500 text-base font-medium text-white hover:bg-red-600 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm transition-colors"> | |
| Delete | |
| </button> | |
| <button id="cancelDelete" type="button" class="w-full inline-flex justify-center rounded-xl border border-gray-300 shadow-sm px-6 py-3 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm transition-colors"> | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Sample customer data | |
| const customers = [ | |
| { id: 1, name: "John Doe", email: "john@example.com", phone: "555-123-4567", status: "Active", joinDate: "2023-01-15" }, | |
| { id: 2, name: "Jane Smith", email: "jane@example.com", phone: "555-987-6543", status: "Active", joinDate: "2023-02-20" }, | |
| { id: 3, name: "Robert Johnson", email: "robert@example.com", phone: "555-456-7890", status: "Inactive", joinDate: "2022-11-05" }, | |
| { id: 4, name: "Emily Davis", email: "emily@example.com", phone: "555-789-0123", status: "Pending", joinDate: "2023-03-10" }, | |
| { id: 5, name: "Michael Wilson", email: "michael@example.com", phone: "555-234-5678", status: "Active", joinDate: "2023-01-30" }, | |
| { id: 6, name: "Sarah Brown", email: "sarah@example.com", phone: "555-876-5432", status: "Banned", joinDate: "2022-12-15" }, | |
| { id: 7, name: "David Taylor", email: "david@example.com", phone: "555-345-6789", status: "Active", joinDate: "2023-02-25" }, | |
| { id: 8, name: "Jessica Martinez", email: "jessica@example.com", phone: "555-765-4321", status: "Pending", joinDate: "2023-03-05" }, | |
| { id: 9, name: "Thomas Anderson", email: "thomas@example.com", phone: "555-456-1234", status: "Inactive", joinDate: "2022-10-20" }, | |
| { id: 10, name: "Lisa Jackson", email: "lisa@example.com", phone: "555-654-3210", status: "Active", joinDate: "2023-01-10" }, | |
| { id: 11, name: "William White", email: "william@example.com", phone: "555-567-8901", status: "Active", joinDate: "2023-02-15" }, | |
| { id: 12, name: "Karen Harris", email: "karen@example.com", phone: "555-543-2109", status: "Banned", joinDate: "2022-11-30" }, | |
| { id: 13, name: "Christopher Clark", email: "chris@example.com", phone: "555-678-9012", status: "Active", joinDate: "2023-03-01" }, | |
| { id: 14, name: "Amanda Lewis", email: "amanda@example.com", phone: "555-432-1098", status: "Pending", joinDate: "2023-03-15" }, | |
| { id: 15, name: "Matthew Robinson", email: "matt@example.com", phone: "555-789-1234", status: "Active", joinDate: "2023-01-20" }, | |
| { id: 16, name: "Stephanie Walker", email: "stephanie@example.com", phone: "555-321-0987", status: "Inactive", joinDate: "2022-12-01" }, | |
| { id: 17, name: "Daniel Young", email: "daniel@example.com", phone: "555-890-1234", status: "Active", joinDate: "2023-02-10" }, | |
| { id: 18, name: "Nicole King", email: "nicole@example.com", phone: "555-210-9876", status: "Active", joinDate: "2023-01-05" }, | |
| { id: 19, name: "Kevin Wright", email: "kevin@example.com", phone: "555-901-2345", status: "Banned", joinDate: "2022-11-15" }, | |
| { id: 20, name: "Rachel Scott", email: "rachel@example.com", phone: "555-109-8765", status: "Active", joinDate: "2023-02-28" } | |
| ]; | |
| // DOM elements | |
| const customerList = document.getElementById('customerList'); | |
| const searchInput = document.getElementById('searchInput'); | |
| const prevPageBtn = document.getElementById('prevPage'); | |
| const nextPageBtn = document.getElementById('nextPage'); | |
| const addCustomerBtn = document.getElementById('addCustomerBtn'); | |
| const customerModal = document.getElementById('customerModal'); | |
| const confirmModal = document.getElementById('confirmModal'); | |
| const closeModalBtn = document.getElementById('closeModal'); | |
| const cancelModalBtn = document.getElementById('cancelModal'); | |
| const saveCustomerBtn = document.getElementById('saveCustomer'); | |
| const confirmDeleteBtn = document.getElementById('confirmDelete'); | |
| const cancelDeleteBtn = document.getElementById('cancelDelete'); | |
| const customerForm = document.getElementById('customerForm'); | |
| const modalTitle = document.getElementById('modalTitle'); | |
| const customerIdInput = document.getElementById('customerId'); | |
| const emptyState = document.getElementById('emptyState'); | |
| const resetFiltersBtn = document.getElementById('resetFiltersBtn'); | |
| const statusFilters = document.querySelectorAll('input[name="status-filter"]'); | |
| // Table state | |
| let currentPage = 1; | |
| const itemsPerPage = 9; | |
| let filteredCustomers = [...customers]; | |
| let customerToDelete = null; | |
| let currentStatusFilter = 'all'; | |
| // Initialize the table | |
| function initTable() { | |
| renderCustomerCards(); | |
| setupEventListeners(); | |
| updatePagination(); | |
| } | |
| // Render customer cards | |
| function renderCustomerCards() { | |
| customerList.innerHTML = ''; | |
| // Get customers for current page | |
| const startIndex = (currentPage - 1) * itemsPerPage; | |
| const endIndex = startIndex + itemsPerPage; | |
| const paginatedCustomers = filteredCustomers.slice(startIndex, endIndex); | |
| // Show empty state if no customers | |
| if (filteredCustomers.length === 0) { | |
| emptyState.classList.remove('hidden'); | |
| customerList.classList.add('hidden'); | |
| } else { | |
| emptyState.classList.add('hidden'); | |
| customerList.classList.remove('hidden'); | |
| } | |
| // Render cards | |
| paginatedCustomers.forEach(customer => { | |
| const card = document.createElement('div'); | |
| card.className = 'customer-card bg-white p-5 fade-in'; | |
| card.innerHTML = ` | |
| <div class="flex items-start justify-between mb-4"> | |
| <div> | |
| <h3 class="text-lg font-semibold text-gray-900">${customer.name}</h3> | |
| <p class="text-sm text-gray-500">ID: ${customer.id}</p> | |
| </div> | |
| <span class="status-badge ${ | |
| customer.status === 'Active' ? 'bg-green-100 text-green-800' : | |
| customer.status === 'Inactive' ? 'bg-gray-100 text-gray-800' : | |
| customer.status === 'Pending' ? 'bg-yellow-100 text-yellow-800' : | |
| 'bg-red-100 text-red-800' | |
| }"> | |
| ${customer.status} | |
| </span> | |
| </div> | |
| <div class="space-y-2 mb-5"> | |
| <div class="flex items-center text-sm text-gray-600"> | |
| <i class="fas fa-envelope mr-2 text-gray-400"></i> | |
| <span>${customer.email}</span> | |
| </div> | |
| <div class="flex items-center text-sm text-gray-600"> | |
| <i class="fas fa-phone mr-2 text-gray-400"></i> | |
| <span>${customer.phone}</span> | |
| </div> | |
| <div class="flex items-center text-sm text-gray-600"> | |
| <i class="fas fa-calendar-day mr-2 text-gray-400"></i> | |
| <span>Joined ${formatDate(customer.joinDate)}</span> | |
| </div> | |
| </div> | |
| <div class="flex justify-end space-x-2"> | |
| <button class="action-button w-10 h-10 flex items-center justify-center text-blue-500 edit-btn" data-id="${customer.id}"> | |
| <i class="fas fa-pencil-alt"></i> | |
| </button> | |
| <button class="action-button w-10 h-10 flex items-center justify-center text-red-500 delete-btn" data-id="${customer.id}"> | |
| <i class="fas fa-trash-alt"></i> | |
| </button> | |
| </div> | |
| `; | |
| customerList.appendChild(card); | |
| }); | |
| // Update pagination buttons | |
| prevPageBtn.disabled = currentPage === 1; | |
| nextPageBtn.disabled = endIndex >= filteredCustomers.length; | |
| } | |
| // Format date for display | |
| function formatDate(dateString) { | |
| const options = { year: 'numeric', month: 'short', day: 'numeric' }; | |
| return new Date(dateString).toLocaleDateString(undefined, options); | |
| } | |
| // Update pagination controls | |
| function updatePagination() { | |
| const pageNumbers = document.querySelector('.flex.gap-1'); | |
| pageNumbers.innerHTML = ''; | |
| const totalPages = Math.ceil(filteredCustomers.length / itemsPerPage); | |
| const maxVisiblePages = 5; | |
| let startPage, endPage; | |
| if (totalPages <= maxVisiblePages) { | |
| startPage = 1; | |
| endPage = totalPages; | |
| } else { | |
| const maxPagesBeforeCurrent = Math.floor(maxVisiblePages / 2); | |
| const maxPagesAfterCurrent = Math.ceil(maxVisiblePages / 2) - 1; | |
| if (currentPage <= maxPagesBeforeCurrent) { | |
| startPage = 1; | |
| endPage = maxVisiblePages; | |
| } else if (currentPage + maxPagesAfterCurrent >= totalPages) { | |
| startPage = totalPages - maxVisiblePages + 1; | |
| endPage = totalPages; | |
| } else { | |
| startPage = currentPage - maxPagesBeforeCurrent; | |
| endPage = currentPage + maxPagesAfterCurrent; | |
| } | |
| } | |
| // Previous ellipsis | |
| if (startPage > 1) { | |
| const ellipsis = document.createElement('span'); | |
| ellipsis.className = 'w-10 h-10 flex items-center justify-center text-gray-500'; | |
| ellipsis.textContent = '...'; | |
| pageNumbers.appendChild(ellipsis); | |
| } | |
| // Page numbers | |
| for (let i = startPage; i <= endPage; i++) { | |
| const pageBtn = document.createElement('button'); | |
| pageBtn.className = `w-10 h-10 flex items-center justify-center rounded-full ${ | |
| i === currentPage ? 'bg-blue-500 text-white' : 'text-gray-700 hover:bg-gray-100' | |
| }`; | |
| pageBtn.textContent = i; | |
| pageBtn.addEventListener('click', () => { | |
| currentPage = i; | |
| renderCustomerCards(); | |
| }); | |
| pageNumbers.appendChild(pageBtn); | |
| } | |
| // Next ellipsis | |
| if (endPage < totalPages) { | |
| const ellipsis = document.createElement('span'); | |
| ellipsis.className = 'w-10 h-10 flex items-center justify-center text-gray-500'; | |
| ellipsis.textContent = '...'; | |
| pageNumbers.appendChild(ellipsis); | |
| } | |
| } | |
| // Filter customers based on search input and status | |
| function filterCustomers() { | |
| const searchTerm = searchInput.value.toLowerCase(); | |
| filteredCustomers = customers.filter(customer => { | |
| const matchesSearch = !searchTerm || | |
| customer.name.toLowerCase().includes(searchTerm) || | |
| customer.email.toLowerCase().includes(searchTerm) || | |
| customer.phone.includes(searchTerm); | |
| const matchesStatus = currentStatusFilter === 'all' || | |
| customer.status === currentStatusFilter; | |
| return matchesSearch && matchesStatus; | |
| }); | |
| currentPage = 1; | |
| renderCustomerCards(); | |
| updatePagination(); | |
| } | |
| // Open modal for adding/editing customer | |
| function openCustomerModal(customer = null) { | |
| if (customer) { | |
| modalTitle.textContent = 'Edit Customer'; | |
| customerIdInput.value = customer.id; | |
| document.getElementById('name').value = customer.name; | |
| document.getElementById('email').value = customer.email; | |
| document.getElementById('phone').value = customer.phone; | |
| document.getElementById('status').value = customer.status; | |
| document.getElementById('joinDate').value = customer.joinDate; | |
| } else { | |
| modalTitle.textContent = 'New Customer'; | |
| customerForm.reset(); | |
| customerIdInput.value = ''; | |
| document.getElementById('joinDate').value = new Date().toISOString().split('T')[0]; | |
| document.getElementById('status').value = 'Active'; | |
| } | |
| customerModal.classList.remove('hidden'); | |
| } | |
| // Save customer (add or update) | |
| function saveCustomer() { | |
| const id = customerIdInput.value; | |
| const name = document.getElementById('name').value; | |
| const email = document.getElementById('email').value; | |
| const phone = document.getElementById('phone').value; | |
| const status = document.getElementById('status').value; | |
| const joinDate = document.getElementById('joinDate').value; | |
| if (!name || !email || !phone || !status || !joinDate) { | |
| alert('Please fill in all fields'); | |
| return; | |
| } | |
| if (id) { | |
| // Update existing customer | |
| const index = customers.findIndex(c => c.id == id); | |
| if (index !== -1) { | |
| customers[index] = { id: parseInt(id), name, email, phone, status, joinDate }; | |
| } | |
| } else { | |
| // Add new customer | |
| const newId = Math.max(...customers.map(c => c.id)) + 1; | |
| customers.push({ id: newId, name, email, phone, status, joinDate }); | |
| } | |
| customerModal.classList.add('hidden'); | |
| filterCustomers(); | |
| } | |
| // Delete customer | |
| function deleteCustomer() { | |
| if (!customerToDelete) return; | |
| const index = customers.findIndex(c => c.id == customerToDelete); | |
| if (index !== -1) { | |
| customers.splice(index, 1); | |
| } | |
| confirmModal.classList.add('hidden'); | |
| customerToDelete = null; | |
| filterCustomers(); | |
| } | |
| // Setup event listeners | |
| function setupEventListeners() { | |
| // Search input | |
| searchInput.addEventListener('input', filterCustomers); | |
| // Pagination buttons | |
| prevPageBtn.addEventListener('click', () => { | |
| if (currentPage > 1) { | |
| currentPage--; | |
| renderCustomerCards(); | |
| } | |
| }); | |
| nextPageBtn.addEventListener('click', () => { | |
| const totalPages = Math.ceil(filteredCustomers.length / itemsPerPage); | |
| if (currentPage < totalPages) { | |
| currentPage++; | |
| renderCustomerCards(); | |
| } | |
| }); | |
| // Status filter | |
| statusFilters.forEach(filter => { | |
| filter.addEventListener('change', () => { | |
| currentStatusFilter = filter.value; | |
| filterCustomers(); | |
| }); | |
| }); | |
| // Add customer button | |
| addCustomerBtn.addEventListener('click', () => openCustomerModal()); | |
| // Edit and delete buttons (delegated) | |
| customerList.addEventListener('click', (e) => { | |
| if (e.target.classList.contains('edit-btn') || e.target.closest('.edit-btn')) { | |
| const btn = e.target.classList.contains('edit-btn') ? e.target : e.target.closest('.edit-btn'); | |
| const customerId = parseInt(btn.dataset.id); | |
| const customer = customers.find(c => c.id === customerId); | |
| if (customer) openCustomerModal(customer); | |
| } | |
| if (e.target.classList.contains('delete-btn') || e.target.closest('.delete-btn')) { | |
| const btn = e.target.classList.contains('delete-btn') ? e.target : e.target.closest('.delete-btn'); | |
| customerToDelete = parseInt(btn.dataset.id); | |
| confirmModal.classList.remove('hidden'); | |
| } | |
| }); | |
| // Modal controls | |
| closeModalBtn.addEventListener('click', () => customerModal.classList.add('hidden')); | |
| cancelModalBtn.addEventListener('click', () => customerModal.classList.add('hidden')); | |
| saveCustomerBtn.addEventListener('click', saveCustomer); | |
| // Confirmation modal controls | |
| confirmDeleteBtn.addEventListener('click', deleteCustomer); | |
| cancelDeleteBtn.addEventListener('click', () => { | |
| confirmModal.classList.add('hidden'); | |
| customerToDelete = null; | |
| }); | |
| // Reset filters button | |
| resetFiltersBtn.addEventListener('click', () => { | |
| searchInput.value = ''; | |
| document.getElementById('filter-all').checked = true; | |
| currentStatusFilter = 'all'; | |
| filterCustomers(); | |
| }); | |
| // Close modals when clicking outside | |
| window.addEventListener('click', (e) => { | |
| if (e.target === customerModal) customerModal.classList.add('hidden'); | |
| if (e.target === confirmModal) confirmModal.classList.add('hidden'); | |
| }); | |
| } | |
| // Initialize the table when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', initTable); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=nj22A/customer-database" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |