document.addEventListener('DOMContentLoaded', () => { const isAdminView = document.body.classList.contains('admin-view'); const createForm = document.getElementById('create-form'); const ticketsContainer = document.getElementById('tickets-container'); const statusFilter = document.getElementById('status-filter'); const categoryFilter = document.getElementById('category-filter'); const searchInput = document.getElementById('search-input'); // Webhook configuration - will be loaded dynamically let webhookConfig = null; // --- Ticket Modal Elements --- const openModalBtn = document.getElementById('open-modal-btn'); const modal = document.getElementById('create-ticket-modal'); const closeModalBtn = document.getElementById('close-modal-btn'); // --- PFP Modal Elements --- const updatePfpBtn = document.getElementById('update-pfp-btn'); const pfpModal = document.getElementById('update-pfp-modal'); const closePfpModalBtn = document.getElementById('close-pfp-modal-btn'); const dropZone = document.getElementById('drop-zone'); const fileInput = document.getElementById('pfp-file-input'); const uploadPfpBtn = document.getElementById('upload-pfp-btn'); let selectedPfpFile = null; let allTickets = []; // --- Helper --- function formatStatusClass(status) { if (!status) return 'Unknown'; return status .split(' ') .map(s => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()) .join('-'); } // --- Webhook Functions --- async function loadWebhookConfig() { try { const response = await fetch('/api/webhooks'); if (response.ok) { webhookConfig = await response.json(); console.log('Webhook configuration loaded:', webhookConfig.configured); } else { console.warn('Failed to load webhook configuration'); webhookConfig = { admin: null, user: null, configured: { admin: false, user: false } }; } } catch (error) { console.error('Error loading webhook configuration:', error); webhookConfig = { admin: null, user: null, configured: { admin: false, user: false } }; } } async function sendWebhookNotification(webhookUrl, message) { if (!webhookUrl) { console.log('No webhook URL provided, skipping notification'); return; } try { const response = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(message) }); if (!response.ok) { console.error(`Webhook notification failed: ${response.status} ${response.statusText}`); } else { console.log('Webhook notification sent successfully'); } } catch (error) { console.error('Failed to send webhook notification:', error); } } async function sendAdminWebhook(ticket) { if (!webhookConfig?.admin) { console.log('Admin webhook not configured, skipping notification'); return; } const message = { embeds: [{ title: '🎫 New Ticket Created', description: `**Ticket #${ticket.id}**: ${ticket.title}`, fields: [ { name: '👤 Requester', value: 'Laila' }, { name: '📊 Status', value: ticket.status || 'Open', inline: true }, { name: '⚡ Priority', value: ticket.priority || 'None', inline: true }, { name: '📂 Category', value: ticket.category || 'None', inline: true }, { name: '📝 Description', value: ticket.description.length > 100 ? ticket.description.substring(0, 100) + '...' : ticket.description, inline: false } ], timestamp: new Date().toISOString(), color: 0x00ff00, footer: { text: 'Ticket System', icon_url: 'https://cdn-icons-png.flaticon.com/512/2666/2666505.png' } }] }; await sendWebhookNotification(webhookConfig.admin, message); } // --- Ticket Modal Open/Close --- function openModal() { if (modal) { const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth; modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; if (scrollBarWidth > 0) { document.body.style.paddingRight = scrollBarWidth + 'px'; } } } function closeModal() { if (modal) { modal.style.display = 'none'; document.body.style.overflow = ''; document.documentElement.style.overflow = ''; document.body.style.paddingRight = ''; } } function outsideClickListener(event) { if (event.target === modal) closeModal(); } function escKeyListener(event) { if (event.key === 'Escape' && modal.style.display === 'flex') { closeModal(); } } if (openModalBtn) openModalBtn.addEventListener('click', openModal); if (closeModalBtn) closeModalBtn.addEventListener('click', closeModal); if (modal) { window.addEventListener('click', outsideClickListener); window.addEventListener('keydown', escKeyListener); } // --- PFP Modal Open/Close --- function openPfpModal() { if (pfpModal) { const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth; pfpModal.style.display = 'flex'; document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; if (scrollBarWidth > 0) { document.body.style.paddingRight = scrollBarWidth + 'px'; } } } function closePfpModal() { if (pfpModal) { pfpModal.style.display = 'none'; document.body.style.overflow = ''; document.documentElement.style.overflow = ''; document.body.style.paddingRight = ''; } } if (updatePfpBtn) updatePfpBtn.addEventListener('click', openPfpModal); if (closePfpModalBtn) closePfpModalBtn.addEventListener('click', closePfpModal); if (pfpModal) { window.addEventListener('click', (e) => { if (e.target === pfpModal) closePfpModal(); }); window.addEventListener('keydown', (e) => { if (e.key === 'Escape' && pfpModal.style.display === 'flex') closePfpModal(); }); } // --- Drag & Drop Logic --- if (dropZone) { dropZone.addEventListener('click', () => fileInput.click()); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.style.borderColor = '#10b981'; }); dropZone.addEventListener('dragleave', () => { dropZone.style.borderColor = '#4b5563'; }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.style.borderColor = '#4b5563'; const file = e.dataTransfer.files[0]; if (file && file.type.startsWith('image/')) { selectedPfpFile = file; dropZone.querySelector('p').textContent = file.name; } }); } if (fileInput) { fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file && file.type.startsWith('image/')) { selectedPfpFile = file; dropZone.querySelector('p').textContent = file.name; } }); } // --- Upload PFP --- if (uploadPfpBtn) { uploadPfpBtn.addEventListener('click', async () => { if (!selectedPfpFile) { alert('Please select an image first.'); return; } const formData = new FormData(); formData.append('pfp', selectedPfpFile, 'user.jpg'); try { const res = await fetch('/api/upload-pfp', { method: 'POST', body: formData }); if (!res.ok) throw new Error('Upload failed'); alert('Profile picture updated!'); closePfpModal(); window.location.reload(); } catch (err) { console.error(err); alert('Error uploading profile picture'); } }); } // --- API --- async function fetchTickets() { try { const res = await fetch('/api/tickets'); return await res.json(); } catch (err) { console.error('Error fetching tickets:', err); return []; } } async function createTicket(ticket) { try { const res = await fetch('/api/tickets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(ticket) }); return await res.json(); } catch (err) { console.error('Error creating ticket:', err); throw err; } } // --- Date Formatting --- function formatDate(dateStr) { return new Date(dateStr).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); } // --- Render --- function renderFilteredTickets() { const status = statusFilter?.value || 'all'; const category = categoryFilter?.value || 'all'; const search = searchInput?.value.trim().toLowerCase() || ''; const openTickets = allTickets.filter(ticket => (ticket.status || '').toLowerCase() !== 'closed' ); const closedTickets = allTickets.filter(ticket => (ticket.status || '').toLowerCase() === 'closed' ); const filteredOpen = openTickets.filter(ticket => { const ticketStatus = (ticket.status || '').toLowerCase(); const ticketCategory = (ticket.category || '').toLowerCase(); const ticketTitle = (ticket.title || '').toLowerCase(); const ticketDescription = (ticket.description || '').toLowerCase(); const matchesStatus = status === 'all' || ticketStatus === status.toLowerCase(); const matchesCategory = category === 'all' || ticketCategory === category.toLowerCase(); const matchesSearch = ticketTitle.includes(search) || ticketDescription.includes(search); return matchesStatus && matchesCategory && matchesSearch; }); const filteredClosed = closedTickets.filter(ticket => { const ticketCategory = (ticket.category || '').toLowerCase(); const ticketTitle = (ticket.title || '').toLowerCase(); const ticketDescription = (ticket.description || '').toLowerCase(); const matchesCategory = category === 'all' || ticketCategory === category.toLowerCase(); const matchesSearch = ticketTitle.includes(search) || ticketDescription.includes(search); return matchesCategory && matchesSearch; }); renderTickets([...filteredOpen, ...filteredClosed]); } function renderTickets(tickets) { const openContainer = document.getElementById('tickets-container'); const closedContainer = document.getElementById('closed-tickets-container'); openContainer.innerHTML = ''; closedContainer.innerHTML = ''; if (!tickets.length) { openContainer.innerHTML = `
Try changing your filters or create a new ticket
${ticket.description}