Greg's Web Consulting, Photography and Computer Repair

Bringing Information to the web, One Page at a time


customer_service

Ticket Management System <!– –> body { background-color: #001f3f; /* Dark blue */ color: white; font-family: “Segoe UI”, Tahoma, Geneva, Verdana, sans-serif; } h1, h2 { font-weight: 600; } /* Status summary boxes */ .status-box { border-radius: 8px; padding: 1.5rem 1rem; color: white; text-align: center; font-weight: 600; cursor: pointer; user-select: none; transition: background-color 0.3s ease; } .status-open { background-color: #dc3545; /* Bootstrap danger red */ color: black; } .status-closed { background-color: #198754; /* Bootstrap green */ color: white; } .status-pending { background-color: #fd7e14; /* Bootstrap orange */ color: black; } .status-box:hover { filter: brightness(0.85); } .status-count { font-size: 2.2rem; margin-top: 0.25rem; } /* Ticket card */ .ticket-card { background: #ffffff20; /* White with transparency */ border-radius: 6px; border: 1px solid #444; margin-bottom: 1rem; box-shadow: 0 2px 4px rgb(0 0 0 / 0.1); transition: box-shadow 0.2s ease-in-out; color: white; position: relative; padding-bottom: 2.5rem; } .ticket-card:hover { box-shadow: 0 4px 12px rgb(0 0 0 / 0.3); } .ticket-ribbon { height: 6px; border-top-left-radius: 6px; border-top-right-radius: 6px; } .ribbon-open { background-color: #0d6efd; /* Bootstrap primary blue */ } .ribbon-pending { background-color: #ffc107; /* Bootstrap warning yellow */ } .ribbon-closed { background-color: #6c757d; /* Bootstrap secondary gray */ } .ticket-body { padding: 1rem 1.25rem; } .ticket-details { white-space: pre-wrap; /* preserve line breaks */ margin-bottom: 0.75rem; } .edit-ticket-btn { position: absolute; bottom: 0.75rem; right: 1rem; font-size: 0.8rem; } /* Customer table adjustments */ .customer-name { cursor: pointer; color: #0d6efd; text-decoration: underline; } .customer-name:hover { color: #82b1ff; } /* Modal styling */ .modal-content { border-radius: 8px; background-color: #001f3f; color: white; } /* Modal ticket list */ .modal-ticket-card { border: 1px solid #444; border-radius: 5px; padding: 0.75rem 1rem; margin-bottom: 0.75rem; background: #002a5c; } .modal-ticket-ribbon { height: 5px; border-radius: 5px 5px 0 0; } .text-muted { font-size: 0.9rem; color: #bbb; } /* Form controls */ .form-control, .form-select { background-color: #004080; color: white; border: 1px solid #0059b3; } .form-control::placeholder { color: #aad4ff; } .form-control:focus, .form-select:focus { background-color: #0059b3; border-color: #3399ff; color: white; box-shadow: none; } /* Delete Customer button style */ .delete-customer-btn { font-size: 0.8rem; padding: 0.25rem 0.5rem; background-color: #dc3545; /* Bootstrap danger red */ color: white; border: none; border-radius: 0.25rem; cursor: pointer; transition: background-color 0.3s ease; } .delete-customer-btn:hover { background-color: #b02a37; /* Darker red on hover */ } /* Optional: add some padding to the table cells holding the button */ #customerTableBody td:last-child { text-align: center; vertical-align: middle; padding: 0.3rem 0.5rem; }

Greg’s Web Design KC
Ticket Management System

Open Tickets
0
Pending Tickets
0
Closed Tickets
0

Customers

Name Email Actions
<!– –> // Data arrays let customers = JSON.parse(localStorage.getItem(‘customers’)) || [ { id: ‘cindy-edwards’, name: ‘Cindy Edwards’, email: ‘cindy@example.com’ } ]; let tickets = JSON.parse(localStorage.getItem(‘tickets’)) || [ { id: ‘t1’, subject: ‘Sample Ticket 1’, customerId: ‘cindy-edwards’, status: ‘Open’, details: ‘Details about ticket 1…’, createdDate: new Date().toLocaleString() } ]; // Generate unique IDs for tickets/customers function generateId(prefix = ‘id’) { return prefix + ‘-‘ + Math.random().toString(36).substring(2, 9); } // Populate customer dropdown for new and edit ticket modals function populateCustomerDropdown() { const select = document.getElementById(‘ticketCustomer’); select.innerHTML = ‘Select a customer’; customers.forEach(c => { const option = document.createElement(‘option’); option.value = c.id; option.textContent = c.name; select.appendChild(option); }); } function populateEditCustomerDropdown() { const select = document.getElementById(‘editTicketCustomer’); select.innerHTML = ‘Select a customer’; customers.forEach(c => { const option = document.createElement(‘option’); option.value = c.id; option.textContent = c.name; select.appendChild(option); }); } // Render tickets filtered by status open or pending (or all if no filter) function renderTickets(filterStatus = [‘Open’, ‘Pending’]) { const container = document.getElementById(‘ticketContainer’); container.innerHTML = ”; const filteredTickets = tickets.filter(t => filterStatus.includes(t.status) ); if (filteredTickets.length === 0) { container.innerHTML = ‘

No tickets matching the filter.

‘; return; } filteredTickets.forEach(ticket => { const card = document.createElement(‘div’); card.className = ‘ticket-card’; card.innerHTML = `
${ticket.subject}

Status: ${ticket.status}

${ticket.details.replace(/\n/g, ‘
‘)}

Created: ${ticket.createdDate}

`; container.appendChild(card); }); // Attach event listeners to Edit buttons document.querySelectorAll(‘.edit-ticket-btn’).forEach(btn => { btn.addEventListener(‘click’, e => { const ticketId = e.target.getAttribute(‘data-ticket-id’); openEditTicketModal(ticketId); }); }); } // Ribbon color class by status function getRibbonClass(status) { switch (status.toLowerCase()) { case ‘open’: return ‘ribbon-open’; case ‘pending’: return ‘ribbon-pending’; case ‘closed’: return ‘ribbon-closed’; default: return ”; } } // Update status counts summary function updateStatusCounts() { const openCount = tickets.filter(t => t.status.toLowerCase() === ‘open’).length; const pendingCount = tickets.filter(t => t.status.toLowerCase() === ‘pending’).length; const closedCount = tickets.filter(t => t.status.toLowerCase() === ‘closed’).length; document.getElementById(‘openCount’).textContent = openCount; document.getElementById(‘pendingCount’).textContent = pendingCount; document.getElementById(‘closedCount’).textContent = closedCount; } // Render customers table function renderCustomerTable() { const tbody = document.getElementById(‘customerTableBody’); tbody.innerHTML = ”; customers.forEach(cust => { const tr = document.createElement(‘tr’); tr.innerHTML = ` ${cust.name} ${cust.email} `; tbody.appendChild(tr); }); // Attach click listeners to customer names (show tickets) document.querySelectorAll(‘.customer-name’).forEach(elem => { elem.addEventListener(‘click’, e => { const custId = e.target.getAttribute(‘data-customer-id’); showCustomerTicketsModal(custId); }); }); // Attach delete customer listeners document.querySelectorAll(‘.delete-customer-btn’).forEach(btn => { btn.addEventListener(‘click’, e => { const custId = e.target.getAttribute(‘data-customer-id’); if (confirm(‘Delete this customer and all their tickets?’)) { deleteCustomer(custId); } }); }); } // Show tickets modal for a customer function showCustomerTicketsModal(customerId) { const customer = customers.find(c => c.id === customerId); if (!customer) return; const modalTitle = document.getElementById(‘customerTicketsModalLabel’); modalTitle.textContent = `Tickets for ${customer.name}`; const container = document.getElementById(‘customerTicketsContainer’); container.innerHTML = ”; const custTickets = tickets.filter(t => t.customerId === customerId); if (custTickets.length === 0) { container.innerHTML = ‘

No tickets found for this customer.

‘; } else { custTickets.forEach(ticket => { const div = document.createElement(‘div’); div.className = ‘modal-ticket-card’; div.innerHTML = `
${ticket.subject}

Status: ${ticket.status}

${ticket.details.replace(/\n/g, ‘
‘)}

Created: ${ticket.createdDate}

`; container.appendChild(div); }); } // Show modal const modalEl = document.getElementById(‘customerTicketsModal’); const bsModal = new bootstrap.Modal(modalEl); bsModal.show(); } // Delete customer and their tickets function deleteCustomer(customerId) { customers = customers.filter(c => c.id !== customerId); tickets = tickets.filter(t => t.customerId !== customerId); localStorage.setItem(‘customers’, JSON.stringify(customers)); localStorage.setItem(‘tickets’, JSON.stringify(tickets)); updateStatusCounts(); renderTickets(); renderCustomerTable(); populateCustomerDropdown(); populateEditCustomerDropdown(); } // Open Edit Ticket Modal and populate form function openEditTicketModal(ticketId) { const ticket = tickets.find(t => t.id === ticketId); if (!ticket) return; document.getElementById(‘editTicketId’).value = ticket.id; document.getElementById(‘editTicketSubject’).value = ticket.subject; document.getElementById(‘editTicketCustomer’).value = ticket.customerId; document.getElementById(‘editTicketStatus’).value = ticket.status; document.getElementById(‘editTicketDetails’).value = ticket.details; const modalEl = document.getElementById(‘editTicketModal’); const bsModal = new bootstrap.Modal(modalEl); bsModal.show(); } // Form handlers // Add new ticket form document.getElementById(‘newTicketForm’).addEventListener(‘submit’, e => { e.preventDefault(); const subject = document.getElementById(‘ticketSubject’).value.trim(); const customerId = document.getElementById(‘ticketCustomer’).value; const details = document.getElementById(‘ticketDetails’).value.trim(); if (!subject || !customerId || !details) { alert(‘Please fill in all fields.’); return; } const newTicket = { id: generateId(‘t’), subject, customerId, status: ‘Open’, details, createdDate: new Date().toLocaleString() }; tickets.push(newTicket); localStorage.setItem(‘tickets’, JSON.stringify(tickets)); updateStatusCounts(); renderTickets(); // Reset and close modal e.target.reset(); const modalEl = document.getElementById(‘newTicketModal’); const bsModal = bootstrap.Modal.getInstance(modalEl); bsModal.hide(); }); // Add customer form document.getElementById(‘addCustomerForm’).addEventListener(‘submit’, e => { e.preventDefault(); const name = document.getElementById(‘customerName’).value.trim(); const email = document.getElementById(‘customerEmail’).value.trim(); if (!name || !email) { alert(‘Please fill in all fields.’); return; } // Check for duplicate emails if (customers.some(c => c.email.toLowerCase() === email.toLowerCase())) { alert(‘A customer with this email already exists.’); return; } const newCustomer = { id: generateId(‘c’), name, email }; customers.push(newCustomer); localStorage.setItem(‘customers’, JSON.stringify(customers)); renderCustomerTable(); populateCustomerDropdown(); populateEditCustomerDropdown(); // Reset and close modal e.target.reset(); const modalEl = document.getElementById(‘addCustomerModal’); const bsModal = bootstrap.Modal.getInstance(modalEl); bsModal.hide(); }); // Edit ticket form submit document.getElementById(‘editTicketForm’).addEventListener(‘submit’, e => { e.preventDefault(); const id = document.getElementById(‘editTicketId’).value; const subject = document.getElementById(‘editTicketSubject’).value.trim(); const customerId = document.getElementById(‘editTicketCustomer’).value; const status = document.getElementById(‘editTicketStatus’).value; const details = document.getElementById(‘editTicketDetails’).value.trim(); if (!subject || !customerId || !status || !details) { alert(‘Please fill in all fields.’); return; } const ticketIndex = tickets.findIndex(t => t.id === id); if (ticketIndex === -1) return; tickets[ticketIndex] = { …tickets[ticketIndex], subject, customerId, status, details }; localStorage.setItem(‘tickets’, JSON.stringify(tickets)); updateStatusCounts(); renderTickets(); const modalEl = document.getElementById(‘editTicketModal’); const bsModal = bootstrap.Modal.getInstance(modalEl); bsModal.hide(); }); // Filter tickets when clicking status boxes document.getElementById(‘openTicketsBox’).addEventListener(‘click’, () => renderTickets([‘Open’])); document.getElementById(‘pendingTicketsBox’).addEventListener(‘click’, () => renderTickets([‘Pending’])); document.getElementById(‘closedTicketsBox’).addEventListener(‘click’, () => renderTickets([‘Closed’])); // On page load populateCustomerDropdown(); populateEditCustomerDropdown(); updateStatusCounts(); renderTickets(); renderCustomerTable();