Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Admin Pulse Dashboard</title> | |
| <link rel="stylesheet" href="style.css"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="components/header.js"></script> | |
| </head> | |
| <body class="bg-gray-100"> | |
| <custom-header></custom-header> | |
| <main class="max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8"> | |
| <div class="mb-6 flex flex-wrap gap-4 items-center justify-between"> | |
| <div class="flex items-center gap-4"> | |
| <label for="topicFilter">Filter by Topic:</label> | |
| <select id="topicFilter" class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent outline-none"> | |
| <option value="all">All Topics</option> | |
| <option value="1">foodsave</option> | |
| <option value="2">foodstore</option> | |
| <option value="3">store</option> | |
| <option value="4">ocr</option> | |
| <option value="5">cv</option> | |
| <option value="6">thesis</option> | |
| <option value="7">hello</option> | |
| <option value="8">hi</option> | |
| <option value="9">emoji</option> | |
| <option value="10">nine</option> | |
| </select> | |
| </div> | |
| <button id="addMessageBtn" class="flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-green-500 to-emerald-500 text-white rounded-lg hover:from-green-600 hover:to-emerald-600 transition-all"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-plus w-4 h-4"><path d="M5 12h14"></path><path d="M12 5v14"></path></svg> | |
| Add New Message | |
| </button> | |
| </div> | |
| <div class="bg-white rounded-lg shadow-md overflow-hidden"> | |
| <div class="overflow-x-auto"> | |
| <table class="w-full"> | |
| <thead class="bg-gray-50 border-b border-gray-200"> | |
| <tr> | |
| <th class="px-6 py-3 text-left text-gray-700">Topic</th> | |
| <th class="px-6 py-3 text-left text-gray-700">Question</th> | |
| <th class="px-6 py-3 text-left text-gray-700">Reply</th> | |
| <th class="px-6 py-3 text-right text-gray-700">Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody id="messagesTableBody" class="divide-y divide-gray-200"> | |
| <!-- Messages will be loaded dynamically --> | |
| <tr> | |
| <td colspan="4" class="px-6 py-8 text-center text-gray-500"> | |
| <i data-feather="loader" class="animate-spin mx-auto w-6 h-6"></i> | |
| <p class="mt-2">Loading messages...</p> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div class="mt-6 flex items-center justify-between bg-white rounded-lg shadow px-6 py-4"> | |
| <div class="text-gray-600" id="paginationInfo">Loading...</div> | |
| <div class="flex gap-2"> | |
| <button id="prevPageBtn" disabled class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">Previous</button> | |
| <button id="page1Btn" class="px-4 py-2 rounded-lg transition-all bg-gradient-to-r from-green-500 to-emerald-500 text-white">1</button> | |
| <button id="page2Btn" class="px-4 py-2 rounded-lg transition-all border border-gray-300 hover:bg-gray-50">2</button> | |
| <button id="nextPageBtn" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">Next</button> | |
| </div> | |
| </div> | |
| <div class="mt-6 grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div class="bg-white rounded-lg shadow p-6"> | |
| <p class="text-gray-600 mb-1">Total Topics</p> | |
| <p id="totalTopics" class="text-green-600 text-2xl font-bold">-</p> | |
| </div> | |
| <div class="bg-white rounded-lg shadow p-6"> | |
| <p class="text-gray-600 mb-1">Total Messages</p> | |
| <p id="totalMessages" class="text-green-600 text-2xl font-bold">-</p> | |
| </div> | |
| <div class="bg-white rounded-lg shadow p-6"> | |
| <p class="text-gray-600 mb-1">Filtered Messages</p> | |
| <p id="filteredMessages" class="text-green-600 text-2xl font-bold">-</p> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Modal for Add/Edit Message --> | |
| <div id="messageModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center p-4 z-50"> | |
| <div class="bg-white rounded-lg shadow-xl w-full max-w-md"> | |
| <div class="p-6 border-b"> | |
| <h3 class="text-xl font-semibold" id="modalTitle">Add New Message</h3> | |
| </div> | |
| <div class="p-6"> | |
| <form id="messageForm"> | |
| <input type="hidden" id="messageId" value=""> | |
| <div class="mb-4"> | |
| <label for="modalTopic" class="block text-gray-700 mb-2">Topic</label> | |
| <select id="modalTopic" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent outline-none"> | |
| <option value="foodsave">foodsave</option> | |
| <option value="foodstore">foodstore</option> | |
| <option value="store">store</option> | |
| <option value="ocr">ocr</option> | |
| <option value="cv">cv</option> | |
| <option value="thesis">thesis</option> | |
| <option value="hello">hello</option> | |
| <option value="hi">hi</option> | |
| <option value="emoji">emoji</option> | |
| <option value="nine">nine</option> | |
| </select> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="modalQuestion" class="block text-gray-700 mb-2">Question</label> | |
| <textarea id="modalQuestion" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent outline-none" placeholder="Enter question"></textarea> | |
| </div> | |
| <div class="mb-6"> | |
| <label for="modalReply" class="block text-gray-700 mb-2">Reply</label> | |
| <textarea id="modalReply" rows="4" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent outline-none" placeholder="Enter reply"></textarea> | |
| </div> | |
| <div class="flex justify-end gap-3"> | |
| <button type="button" id="cancelModalBtn" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">Cancel</button> | |
| <button type="submit" class="px-4 py-2 bg-gradient-to-r from-green-500 to-emerald-500 text-white rounded-lg hover:from-green-600 hover:to-emerald-600 transition-all">Save Message</button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| feather.replace(); | |
| </script> | |
| <script src="script.js"></script> | |
| <script> | |
| // Enhanced functionality for the dashboard | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // State | |
| let messages = []; | |
| let filteredMessages = []; | |
| let currentPage = 1; | |
| const itemsPerPage = 5; | |
| let selectedTopic = 'all'; | |
| // DOM Elements | |
| const topicFilter = document.getElementById('topicFilter'); | |
| const messagesTableBody = document.getElementById('messagesTableBody'); | |
| const totalTopicsEl = document.getElementById('totalTopics'); | |
| const totalMessagesEl = document.getElementById('totalMessages'); | |
| const filteredMessagesEl = document.getElementById('filteredMessages'); | |
| const paginationInfo = document.getElementById('paginationInfo'); | |
| const prevPageBtn = document.getElementById('prevPageBtn'); | |
| const nextPageBtn = document.getElementById('nextPageBtn'); | |
| const page1Btn = document.getElementById('page1Btn'); | |
| const page2Btn = document.getElementById('page2Btn'); | |
| const addMessageBtn = document.getElementById('addMessageBtn'); | |
| const messageModal = document.getElementById('messageModal'); | |
| const modalTitle = document.getElementById('modalTitle'); | |
| const messageForm = document.getElementById('messageForm'); | |
| const cancelModalBtn = document.getElementById('cancelModalBtn'); | |
| const messageIdInput = document.getElementById('messageId'); | |
| const modalTopicSelect = document.getElementById('modalTopic'); | |
| const modalQuestionTextarea = document.getElementById('modalQuestion'); | |
| const modalReplyTextarea = document.getElementById('modalReply'); | |
| // Initial data (simulating API call) | |
| function loadMessages() { | |
| // Simulate API call with setTimeout | |
| setTimeout(() => { | |
| // Sample data (in real app, fetch from backend) | |
| messages = [ | |
| { id: 1, topic: 'foodsave', question: 'What is foodsave?', reply: 'Foodsave is a platform designed to help reduce food waste by connecting users with surplus food from restaurants and stores.' }, | |
| { id: 2, topic: 'foodstore', question: 'How does the foodstore work?', reply: 'Foodstore allows you to browse and purchase discounted food items that are near their expiration date, helping reduce waste.' }, | |
| { id: 3, topic: 'store', question: 'Where can I find stores near me?', reply: 'You can use our store locator feature in the app to find participating stores in your area.' }, | |
| { id: 4, topic: 'ocr', question: 'What is OCR technology?', reply: 'OCR (Optical Character Recognition) is a technology that converts images of text into editable and searchable text data.' }, | |
| { id: 5, topic: 'cv', question: 'How do I create a CV?', reply: 'A CV should include your personal information, education, work experience, skills, and achievements. Keep it clear and concise.' }, | |
| { id: 6, topic: 'thesis', question: 'What is a thesis?', reply: 'A thesis is a long essay or dissertation involving personal research, written by a candidate for a university degree.' }, | |
| { id: 7, topic: 'hello', question: 'How to say hello in Spanish?', reply: 'In Spanish, hello is "Hola".' }, | |
| { id: 8, topic: 'hi', question: 'What is the difference between hi and hello?', reply: 'Both are greetings, "hi" is more informal while "hello" is more formal.' }, | |
| { id: 9, topic: 'emoji', question: 'What are emojis?', reply: 'Emojis are small digital images or icons used to express an idea or emotion in electronic communication.' }, | |
| { id: 10, topic: 'nine', question: 'What is nine squared?', reply: 'Nine squared (9²) equals 81.' } | |
| ]; | |
| updateUI(); | |
| }, 500); | |
| } | |
| // Update UI based on current state | |
| function updateUI() { | |
| // Filter messages by topic | |
| if (selectedTopic === 'all') { | |
| filteredMessages = [...messages]; | |
| } else { | |
| filteredMessages = messages.filter(msg => msg.topic === selectedTopic); | |
| } | |
| // Update stats | |
| const uniqueTopics = [...new Set(messages.map(msg => msg.topic))]; | |
| totalTopicsEl.textContent = uniqueTopics.length; | |
| totalMessagesEl.textContent = messages.length; | |
| filteredMessagesEl.textContent = filteredMessages.length; | |
| // Update pagination | |
| const totalPages = Math.ceil(filteredMessages.length / itemsPerPage); | |
| const startIndex = (currentPage - 1) * itemsPerPage; | |
| const endIndex = Math.min(startIndex + itemsPerPage, filteredMessages.length); | |
| const pageMessages = filteredMessages.slice(startIndex, endIndex); | |
| // Update pagination info | |
| paginationInfo.textContent = `Showing ${startIndex + 1} to ${endIndex} of ${filteredMessages.length} messages`; | |
| // Update pagination buttons | |
| prevPageBtn.disabled = currentPage === 1; | |
| nextPageBtn.disabled = currentPage === totalPages; | |
| // Update page buttons | |
| page1Btn.textContent = currentPage; | |
| page1Btn.classList.remove('border', 'border-gray-300', 'hover:bg-gray-50'); | |
| page1Btn.classList.add('bg-gradient-to-r', 'from-green-500', 'to-emerald-500', 'text-white'); | |
| if (totalPages > 1) { | |
| page2Btn.style.display = 'inline-block'; | |
| page2Btn.textContent = currentPage + 1; | |
| page2Btn.classList.remove('bg-gradient-to-r', 'from-green-500', 'to-emerald-500', 'text-white'); | |
| page2Btn.classList.add('border', 'border-gray-300', 'hover:bg-gray-50'); | |
| } else { | |
| page2Btn.style.display = 'none'; | |
| } | |
| // Render table rows | |
| messagesTableBody.innerHTML = ''; | |
| if (pageMessages.length === 0) { | |
| messagesTableBody.innerHTML = ` | |
| <tr> | |
| <td colspan="4" class="px-6 py-8 text-center text-gray-500"> | |
| <i data-feather="inbox" class="mx-auto w-8 h-8"></i> | |
| <p class="mt-2">No messages found</p> | |
| </td> | |
| </tr> | |
| `; | |
| } else { | |
| pageMessages.forEach(msg => { | |
| const row = document.createElement('tr'); | |
| row.className = 'hover:bg-gray-50'; | |
| row.innerHTML = ` | |
| <td class="px-6 py-4"> | |
| <span class="inline-block px-3 py-1 bg-green-100 text-green-700 rounded-full">${msg.topic}</span> | |
| </td> | |
| <td class="px-6 py-4"> | |
| <span>${msg.question}</span> | |
| </td> | |
| <td class="px-6 py-4"> | |
| <span class="text-gray-600">${msg.reply}</span> | |
| </td> | |
| <td class="px-6 py-4"> | |
| <div class="flex gap-2 justify-end"> | |
| <button class="edit-btn p-2 bg-gradient-to-r from-green-500 to-emerald-500 text-white rounded hover:from-green-600 hover:to-emerald-600 transition-all" title="Edit" data-id="${msg.id}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pen w-4 h-4"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"></path></svg> | |
| </button> | |
| <button class="delete-btn p-2 bg-red-600 text-white rounded hover:bg-red-700 transition-colors" title="Delete" data-id="${msg.id}"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trash2 lucide-trash-2 w-4 h-4"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path><line x1="10" x2="10" y1="11" y2="17"></line><line x1="14" x2="14" y1="11" y2="17"></line></svg> | |
| </button> | |
| </div> | |
| </td> | |
| `; | |
| messagesTableBody.appendChild(row); | |
| }); | |
| } | |
| // Re-attach event listeners to edit/delete buttons | |
| attachRowEventListeners(); | |
| feather.replace(); | |
| } | |
| // Attach event listeners to edit/delete buttons | |
| function attachRowEventListeners() { | |
| document.querySelectorAll('.edit-btn').forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| const id = parseInt(this.getAttribute('data-id')); | |
| const message = messages.find(msg => msg.id === id); | |
| if (message) { | |
| openEditModal(message); | |
| } | |
| }); | |
| }); | |
| document.querySelectorAll('.delete-btn').forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| const id = parseInt(this.getAttribute('data-id')); | |
| if (confirm('Are you sure you want to delete this message?')) { | |
| deleteMessage(id); | |
| } | |
| }); | |
| }); | |
| } | |
| // Open modal for editing | |
| function openEditModal(message) { | |
| modalTitle.textContent = 'Edit Message'; | |
| messageIdInput.value = message.id; | |
| modalTopicSelect.value = message.topic; | |
| modalQuestionTextarea.value = message.question; | |
| modalReplyTextarea.value = message.reply; | |
| messageModal.classList.remove('hidden'); | |
| messageModal.classList.add('flex'); | |
| } | |
| // Open modal for adding | |
| function openAddModal() { | |
| modalTitle.textContent = 'Add New Message'; | |
| messageIdInput.value = ''; | |
| modalTopicSelect.value = 'foodsave'; | |
| modalQuestionTextarea.value = ''; | |
| modalReplyTextarea.value = ''; | |
| messageModal.classList.remove('hidden'); | |
| messageModal.classList.add('flex'); | |
| } | |
| // Delete message | |
| function deleteMessage(id) { | |
| messages = messages.filter(msg => msg.id !== id); | |
| updateUI(); | |
| } | |
| // Save message (add or update) | |
| function saveMessage(id, topic, question, reply) { | |
| if (id) { | |
| // Update existing | |
| const index = messages.findIndex(msg => msg.id === id); | |
| if (index !== -1) { | |
| messages[index] = { id, topic, question, reply }; | |
| } | |
| } else { | |
| // Add new | |
| const newId = messages.length > 0 ? Math.max(...messages.map(msg => msg.id)) + 1 : 1; | |
| messages.push({ id: newId, topic, question, reply }); | |
| } | |
| updateUI(); | |
| closeModal(); | |
| } | |
| // Close modal | |
| function closeModal() { | |
| messageModal.classList.add('hidden'); | |
| messageModal.classList.remove('flex'); | |
| } | |
| // Event Listeners | |
| topicFilter.addEventListener('change', function() { | |
| selectedTopic = this.value; | |
| currentPage = 1; | |
| updateUI(); | |
| }); | |
| prevPageBtn.addEventListener('click', function() { | |
| if (currentPage > 1) { | |
| currentPage--; | |
| updateUI(); | |
| } | |
| }); | |
| nextPageBtn.addEventListener('click', function() { | |
| const totalPages = Math.ceil(filteredMessages.length / itemsPerPage); | |
| if (currentPage < totalPages) { | |
| currentPage++; | |
| updateUI(); | |
| } | |
| }); | |
| page1Btn.addEventListener('click', function() { | |
| currentPage = parseInt(this.textContent); | |
| updateUI(); | |
| }); | |
| page2Btn.addEventListener('click', function() { | |
| currentPage = parseInt(this.textContent); | |
| updateUI(); | |
| }); | |
| addMessageBtn.addEventListener('click', openAddModal); | |
| messageForm.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const id = messageIdInput.value ? parseInt(messageIdInput.value) : null; | |
| const topic = modalTopicSelect.value; | |
| const question = modalQuestionTextarea.value.trim(); | |
| const reply = modalReplyTextarea.value.trim(); | |
| if (!question || !reply) { | |
| alert('Please fill in both question and reply.'); | |
| return; | |
| } | |
| saveMessage(id, topic, question, reply); | |
| }); | |
| cancelModalBtn.addEventListener('click', closeModal); | |
| // Close modal when clicking outside | |
| messageModal.addEventListener('click', function(e) { | |
| if (e.target === messageModal) { | |
| closeModal(); | |
| } | |
| }); | |
| // Load initial data | |
| loadMessages(); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |