// DOM Elements const itemForm = document.getElementById('item-form'); const formTitle = document.getElementById('form-title'); const itemIdInput = document.getElementById('item-id'); const titleInput = document.getElementById('title'); const descriptionInput = document.getElementById('description'); const completedCheckbox = document.getElementById('completed'); const submitBtn = document.getElementById('submit-btn'); const cancelBtn = document.getElementById('cancel-btn'); const itemsTableBody = document.getElementById('items-table-body'); const noItemsMessage = document.getElementById('no-items-message'); const createFirstItemBtn = document.getElementById('create-first-item-btn'); const loadingSpinner = document.getElementById('loading-spinner'); const toastElement = document.getElementById('toast'); const toastTitle = document.getElementById('toast-title'); const toastMessage = document.getElementById('toast-message'); const toastIcon = document.getElementById('toast-icon'); // Create Bootstrap toast instance const toast = new bootstrap.Toast(toastElement, { delay: 3000 }); // API Endpoints const API_URL = '/api/items'; // App State let isEditing = false; let items = []; // Event Listeners document.addEventListener('DOMContentLoaded', fetchItems); itemForm.addEventListener('submit', handleFormSubmit); cancelBtn.addEventListener('click', resetForm); if (createFirstItemBtn) { createFirstItemBtn.addEventListener('click', () => { window.scrollTo({ top: itemForm.offsetTop - 100, behavior: 'smooth' }); titleInput.focus(); }); } // Functions async function fetchItems() { try { showLoading(true); const response = await fetch(API_URL); const data = await response.json(); // Keep a reference to all items items = data; renderItems(items); showLoading(false); } catch (error) { console.error('Error fetching items:', error); showToast('Error', 'Failed to load items. Please try again.', 'error'); showLoading(false); } } function renderItems(items) { itemsTableBody.innerHTML = ''; if (items.length === 0) { noItemsMessage.style.display = 'block'; document.querySelector('.table-responsive').style.display = 'none'; return; } noItemsMessage.style.display = 'none'; document.querySelector('.table-responsive').style.display = 'block'; items.forEach(item => { const row = document.createElement('tr'); row.id = `item-row-${item.id}`; row.innerHTML = ` ${item.id} ${escapeHtml(item.title)} ${escapeHtml(item.description || '-')} ${item.completed ? 'Completed' : 'Pending'}
`; itemsTableBody.appendChild(row); // Add event listeners to buttons const editBtn = row.querySelector('.edit-btn'); const deleteBtn = row.querySelector('.delete-btn'); editBtn.addEventListener('click', () => editItem(item)); deleteBtn.addEventListener('click', () => deleteItem(item.id)); }); } async function handleFormSubmit(event) { event.preventDefault(); const itemData = { title: titleInput.value.trim(), description: descriptionInput.value.trim(), completed: completedCheckbox.checked }; if (!itemData.title) { showToast('Validation Error', 'Title is required', 'error'); titleInput.focus(); return; } try { showLoading(true); if (isEditing) { // Update existing item const itemId = parseInt(itemIdInput.value); await updateItem(itemId, itemData); showToast('Success', 'Item updated successfully!', 'success'); } else { // Create new item await createItem(itemData); showToast('Success', 'New item created successfully!', 'success'); } resetForm(); fetchItems(); } catch (error) { console.error('Error saving item:', error); showToast('Error', 'Failed to save item. Please try again.', 'error'); showLoading(false); } } async function createItem(itemData) { const response = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(itemData) }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to create item'); } return await response.json(); } async function updateItem(itemId, itemData) { const response = await fetch(`${API_URL}/${itemId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(itemData) }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to update item'); } return await response.json(); } async function deleteItem(itemId) { // Create custom confirm dialog if (!confirm('Are you sure you want to delete this item? This action cannot be undone.')) { return; } try { showLoading(true); const response = await fetch(`${API_URL}/${itemId}`, { method: 'DELETE' }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to delete item'); } showToast('Success', 'Item deleted successfully!', 'success'); // Remove item from local array items = items.filter(item => item.id !== itemId); // Re-render items renderItems(items); showLoading(false); } catch (error) { console.error('Error deleting item:', error); showToast('Error', 'Failed to delete item. Please try again.', 'error'); showLoading(false); } } function editItem(item) { // Set form to edit mode isEditing = true; formTitle.innerHTML = 'Edit Item'; submitBtn.innerHTML = 'Update'; cancelBtn.style.display = 'block'; // Populate form with item data itemIdInput.value = item.id; titleInput.value = item.title; descriptionInput.value = item.description || ''; completedCheckbox.checked = item.completed; // Scroll to form window.scrollTo({ top: itemForm.offsetTop - 100, behavior: 'smooth' }); // Add highlight class to row const row = document.getElementById(`item-row-${item.id}`); if (row) { row.classList.add('highlight-row'); setTimeout(() => { row.classList.remove('highlight-row'); }, 2000); } // Focus on title input titleInput.focus(); } function resetForm() { // Reset form state isEditing = false; formTitle.innerHTML = 'Add New Item'; submitBtn.innerHTML = 'Save'; cancelBtn.style.display = 'none'; // Clear form inputs itemForm.reset(); itemIdInput.value = ''; } function showToast(title, message, type = 'info') { toastTitle.textContent = title; toastMessage.textContent = message; // Remove any existing classes toastElement.classList.remove('bg-success', 'bg-danger', 'bg-info', 'bg-warning', 'text-white'); toastIcon.classList.remove('bi-check-circle-fill', 'bi-exclamation-triangle-fill', 'bi-info-circle-fill'); // Add appropriate styling based on type if (type === 'success') { toastElement.classList.add('bg-success', 'text-white'); toastIcon.classList.add('bi-check-circle-fill'); } else if (type === 'error') { toastElement.classList.add('bg-danger', 'text-white'); toastIcon.classList.add('bi-exclamation-triangle-fill'); } else { toastElement.classList.add('bg-info', 'text-white'); toastIcon.classList.add('bi-info-circle-fill'); } toast.show(); } function showLoading(isLoading) { if (isLoading) { loadingSpinner.style.display = 'inline-block'; } else { loadingSpinner.style.display = 'none'; } } // Utility function to prevent XSS function escapeHtml(str) { if (!str) return ''; return str .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }