1356 lines
43 KiB
JavaScript
1356 lines
43 KiB
JavaScript
function showTab(id, event) {
|
|
// Remove active class from all tabs
|
|
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
|
|
|
|
// Add active class to clicked tab
|
|
event.target.classList.add('active');
|
|
|
|
// Hide all content divs
|
|
document.querySelectorAll('.content > div').forEach(div => {
|
|
div.classList.add('hidden');
|
|
div.classList.remove('slide-in');
|
|
});
|
|
|
|
// Show selected content with animation
|
|
const targetDiv = document.getElementById(id);
|
|
targetDiv.classList.remove('hidden');
|
|
setTimeout(() => targetDiv.classList.add('slide-in'), 10);
|
|
|
|
// Load coupon list when "list" tab is shown
|
|
if (id === 'list') {
|
|
loadCodeList();
|
|
// Set up search functionality when list tab is shown
|
|
setupSearchFunctionality();
|
|
}
|
|
|
|
// Handle translation upload tab
|
|
if (id === 'translation-upload') {
|
|
renderTranslationUploadSection();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles the logout process by showing confirmation modal
|
|
*/
|
|
async function handleLogout() {
|
|
document.getElementById('logoutModal').classList.add('show');
|
|
}
|
|
|
|
/**
|
|
* Closes the logout confirmation modal
|
|
*/
|
|
function closeLogoutModal() {
|
|
document.getElementById('logoutModal').classList.remove('show');
|
|
}
|
|
|
|
/**
|
|
* Confirms and executes the logout process
|
|
* Makes API call to logout endpoint and redirects user
|
|
*/
|
|
async function confirmLogout() {
|
|
const logoutBtn = document.querySelector('#logoutModal .btn-danger');
|
|
|
|
// Show loading state
|
|
logoutBtn.classList.add('loading');
|
|
logoutBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Logging out...';
|
|
|
|
try {
|
|
const response = await fetch('/admin/logout', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
showNotification('Logged out successfully! Redirecting...', 'success');
|
|
setTimeout(() => {
|
|
window.location.href = '/login';
|
|
}, 1500);
|
|
} else {
|
|
showNotification('Error during logout. Please try again.', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Logout error:', error);
|
|
showNotification('Network error. Please try again.', 'error');
|
|
} finally {
|
|
// Reset button
|
|
logoutBtn.classList.remove('loading');
|
|
logoutBtn.innerHTML = '<i class="fas fa-sign-out-alt"></i> Logout';
|
|
closeLogoutModal();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates coupon codes based on selected mode (single or bulk)
|
|
* Handles form validation and displays results
|
|
*/
|
|
async function generateCode() {
|
|
const mode = document.querySelector('input[name="genMode"]:checked').value;
|
|
const resultEl = document.getElementById('genResult');
|
|
const btn = event.target;
|
|
|
|
// Show loading state
|
|
btn.classList.add('loading');
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generating...';
|
|
|
|
let payload = new FormData();
|
|
payload.append("mode", mode);
|
|
|
|
if (mode === "bulk") {
|
|
const count = parseInt(document.getElementById('bulkCount').value);
|
|
if (!count || count <= 0) {
|
|
resultEl.innerHTML = `
|
|
<div class="result-card warning">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<strong>Warning!</strong> Please enter a valid number of codes to generate.
|
|
</div>
|
|
`;
|
|
// Reset button
|
|
btn.classList.remove('loading');
|
|
btn.innerHTML = '<i class="fas fa-sparkles"></i> Generate Codes';
|
|
return;
|
|
}
|
|
payload.append("count", count);
|
|
}
|
|
|
|
try {
|
|
const res = await fetch('/generate', {
|
|
method: 'POST',
|
|
body: payload
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (mode === "single") {
|
|
resultEl.innerHTML = `
|
|
<div class="result-card success">
|
|
<i class="fas fa-check-circle"></i>
|
|
<strong>Success!</strong> Generated Code: <code>${data.code}</code>
|
|
</div>
|
|
`;
|
|
} else {
|
|
resultEl.innerHTML = `
|
|
<div class="result-card success">
|
|
<i class="fas fa-check-circle"></i>
|
|
<strong>Success!</strong> Generated ${data.codes.length} codes:
|
|
<div class="code-list">
|
|
${data.codes.map(code => `<div class="code-item"><code>${code}</code></div>`).join('')}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
} catch (error) {
|
|
resultEl.innerHTML = `
|
|
<div class="result-card error">
|
|
<i class="fas fa-exclamation-circle"></i>
|
|
<strong>Error!</strong> Failed to generate codes. Please try again.
|
|
</div>
|
|
`;
|
|
} finally {
|
|
// Reset button
|
|
btn.classList.remove('loading');
|
|
btn.innerHTML = '<i class="fas fa-sparkles"></i> Generate Codes';
|
|
}
|
|
}
|
|
|
|
// Pagination variables
|
|
let currentPage = 1;
|
|
let totalPages = 1;
|
|
let totalCodes = 0;
|
|
const codesPerPage = 20;
|
|
|
|
/**
|
|
* Loads and displays coupon code list with pagination
|
|
*/
|
|
async function loadCodeList(page = 1) {
|
|
try {
|
|
const res = await fetch(`/list?page=${page}&limit=${codesPerPage}`);
|
|
const data = await res.json();
|
|
const tbody = document.querySelector('#codeTable tbody');
|
|
console.log("Loading coupon list...");
|
|
|
|
// Update pagination variables
|
|
currentPage = data.page;
|
|
totalPages = data.total_pages;
|
|
totalCodes = data.total;
|
|
|
|
if (data.codes.length === 0) {
|
|
tbody.innerHTML = `
|
|
<tr>
|
|
<td colspan="3" style="text-align: center; padding: 40px; color: #6b7280;">
|
|
<i class="fas fa-inbox" style="font-size: 48px; margin-bottom: 16px; display: block;"></i>
|
|
No coupon codes found
|
|
</td>
|
|
</tr>
|
|
`;
|
|
updatePaginationInfo();
|
|
updatePaginationControls();
|
|
return;
|
|
}
|
|
|
|
// Render current page rows
|
|
tbody.innerHTML = '';
|
|
data.codes.forEach(item => {
|
|
const usedAtDisplay = item.usage_count > 0 ? (item.used_at ? item.used_at : '--') : '--';
|
|
|
|
const row = `
|
|
<tr data-code="${item.code.toLowerCase()}">
|
|
<td><code style="background: #f1f5f9; padding: 4px 8px; border-radius: 4px; font-weight: 600;">${item.code}</code></td>
|
|
<td>${usedAtDisplay}</td>
|
|
<td>
|
|
<div class="action-buttons">
|
|
<button class="action-btn delete-btn" onclick="deleteCode('${item.code}')" title="Delete Code">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
tbody.innerHTML += row;
|
|
});
|
|
|
|
updatePaginationInfo();
|
|
updatePaginationControls();
|
|
|
|
} catch (error) {
|
|
console.error("Error loading coupon list:", error);
|
|
const tbody = document.querySelector('#codeTable tbody');
|
|
tbody.innerHTML = `
|
|
<tr>
|
|
<td colspan="3" style="text-align: center; padding: 40px; color: #ef4444;">
|
|
<i class="fas fa-exclamation-circle" style="font-size: 48px; margin-bottom: 16px; display: block;"></i>
|
|
Error loading coupon codes
|
|
</td>
|
|
</tr>
|
|
`;
|
|
updatePaginationInfo();
|
|
updatePaginationControls();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggles between single code entry and Excel file upload modes
|
|
*/
|
|
function toggleUploadMode(mode) {
|
|
const singleSection = document.getElementById("add-code-form");
|
|
const excelSection = document.getElementById("file-upload-section");
|
|
|
|
// Update radio option styling
|
|
document.querySelectorAll('input[name="uploadMode"]').forEach(radio => {
|
|
const option = radio.closest('.radio-option');
|
|
if (radio.checked) {
|
|
option.classList.add('active');
|
|
} else {
|
|
option.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
if (mode === "single") {
|
|
singleSection.classList.remove("hidden");
|
|
singleSection.style.display = "block";
|
|
excelSection.classList.add("hidden");
|
|
excelSection.style.display = "none";
|
|
|
|
// Clear any existing file upload data
|
|
clearFile();
|
|
} else if (mode === "excel") {
|
|
singleSection.classList.add("hidden");
|
|
singleSection.style.display = "none";
|
|
excelSection.classList.remove("hidden");
|
|
excelSection.style.display = "block";
|
|
|
|
// Clear single code form
|
|
document.getElementById('new-code').value = '';
|
|
document.getElementById('new-usage').value = '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a new coupon code through the single entry form
|
|
* Validates input and sends data to server
|
|
*/
|
|
async function addNewCode(event) {
|
|
const codeInput = document.getElementById('new-code');
|
|
const usageInput = document.getElementById('new-usage');
|
|
const btn = event.target;
|
|
|
|
const code = codeInput.value.trim().toUpperCase();
|
|
const usage = parseInt(usageInput.value) || 0;
|
|
|
|
// Validation
|
|
if (!code) {
|
|
showNotification('Please enter a coupon code', 'error');
|
|
return;
|
|
}
|
|
|
|
if (code.length < 3) {
|
|
showNotification('Code must be at least 3 characters long', 'error');
|
|
return;
|
|
}
|
|
|
|
if (usage < 0) {
|
|
showNotification('Usage count cannot be negative', 'error');
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
btn.classList.add('loading');
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Adding...';
|
|
|
|
try {
|
|
const response = await fetch('/add-code', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
code: code,
|
|
usage: usage
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
showNotification('Code added successfully!', 'success');
|
|
|
|
// Clear inputs
|
|
codeInput.value = '';
|
|
usageInput.value = '';
|
|
|
|
// Reload code list if on list tab
|
|
if (typeof loadCodeList === 'function') {
|
|
loadCodeList(currentPage || 1);
|
|
}
|
|
|
|
// DO NOT hide the form - keep it visible for more entries
|
|
|
|
} else {
|
|
const error = await response.json();
|
|
showNotification(error.detail || 'Failed to add code', 'error');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error adding code:', error);
|
|
showNotification('Network error. Please try again.', 'error');
|
|
} finally {
|
|
// Reset button
|
|
btn.classList.remove('loading');
|
|
btn.innerHTML = '<i class="fas fa-plus"></i> Add Code';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initiates the delete process for a coupon code
|
|
* Shows confirmation modal
|
|
*/
|
|
function deleteCode(code) {
|
|
currentDeleteCode = code;
|
|
document.getElementById('deleteCodeName').textContent = code;
|
|
document.getElementById('deleteModal').classList.add('show');
|
|
}
|
|
|
|
/**
|
|
* Confirms and executes coupon code deletion
|
|
* Makes API call and updates the list
|
|
*/
|
|
async function confirmDeleteCode() {
|
|
const btn = event.target;
|
|
|
|
// Show loading state
|
|
btn.classList.add('loading');
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Deleting...';
|
|
|
|
try {
|
|
const response = await fetch(`/delete-code/${currentDeleteCode}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
if (response.ok) {
|
|
showNotification('Code deleted successfully!', 'success');
|
|
closeDeleteModal();
|
|
loadCodeList(currentPage);
|
|
} else {
|
|
const error = await response.json();
|
|
showNotification(error.detail || 'Failed to delete code', 'error');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error deleting code:', error);
|
|
showNotification('Network error. Please try again.', 'error');
|
|
} finally {
|
|
// Reset button
|
|
btn.classList.remove('loading');
|
|
btn.innerHTML = '<i class="fas fa-trash"></i> Delete Code';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Closes the delete confirmation modal and resets state
|
|
*/
|
|
function closeDeleteModal() {
|
|
document.getElementById('deleteModal').classList.remove('show');
|
|
currentDeleteCode = null;
|
|
}
|
|
|
|
/**
|
|
* Displays a notification message with specified type and auto-dismiss
|
|
*/
|
|
function showNotification(message, type = 'info') {
|
|
// Remove existing notifications
|
|
const existingNotification = document.querySelector('.notification');
|
|
if (existingNotification) {
|
|
existingNotification.remove();
|
|
}
|
|
|
|
// Create notification element
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification ${type}`;
|
|
notification.innerHTML = `
|
|
<div style="
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
z-index: 9999;
|
|
padding: 15px 20px;
|
|
border-radius: 8px;
|
|
color: white;
|
|
font-weight: 600;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
|
animation: slideInRight 0.3s ease-out;
|
|
background: ${type === 'success' ? 'linear-gradient(135deg, #10b981, #059669)' :
|
|
type === 'error' ? 'linear-gradient(135deg, #ef4444, #dc2626)' :
|
|
'linear-gradient(135deg, #3b82f6, #1d4ed8)'};
|
|
">
|
|
<i class="fas fa-${type === 'success' ? 'check-circle' :
|
|
type === 'error' ? 'exclamation-circle' :
|
|
'info-circle'}"></i>
|
|
${message}
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
// Auto-remove after 4 seconds
|
|
setTimeout(() => {
|
|
if (notification.parentNode) {
|
|
notification.remove();
|
|
}
|
|
}, 4000);
|
|
}
|
|
|
|
// Close modals when clicking outside
|
|
document.addEventListener('click', function(event) {
|
|
const deleteModal = document.getElementById('deleteModal');
|
|
|
|
if (event.target === deleteModal) {
|
|
closeDeleteModal();
|
|
}
|
|
});
|
|
|
|
// Close modals with Escape key
|
|
document.addEventListener('keydown', function(event) {
|
|
if (event.key === 'Escape') {
|
|
closeDeleteModal();
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Filters coupon codes based on search query
|
|
* Makes API call to search endpoint and displays results
|
|
*/
|
|
async function filterCoupons(query) {
|
|
console.log('filterCoupons called with query:', query); // Debug
|
|
|
|
const tbody = document.querySelector('#codeTable tbody');
|
|
const searchResultInfo = document.getElementById('searchResultInfo');
|
|
const noResultsInfo = document.getElementById('noResultsInfo');
|
|
const searchResultText = document.getElementById('searchResultText');
|
|
|
|
console.log('Elements found:', { tbody, searchResultInfo, noResultsInfo, searchResultText }); // Debug
|
|
|
|
if (!query.trim()) {
|
|
console.log('Empty query, calling showAllCoupons'); // Debug
|
|
showAllCoupons();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log('Making fetch request to:', `/search-codes?query=${encodeURIComponent(query)}`); // Debug
|
|
const res = await fetch(`/search-codes?query=${encodeURIComponent(query)}`);
|
|
console.log('Response status:', res.status); // Debug
|
|
|
|
const results = await res.json();
|
|
console.log('Search results:', results); // Debug
|
|
|
|
tbody.innerHTML = '';
|
|
|
|
if (results.length === 0) {
|
|
console.log('No results found, showing no results message'); // Debug
|
|
noResultsInfo.classList.add('show');
|
|
searchResultInfo.classList.remove('show');
|
|
return;
|
|
}
|
|
|
|
// Show rows with action buttons
|
|
results.forEach(item => {
|
|
const row = `
|
|
<tr data-code="${item.code.toLowerCase()}" class="highlight">
|
|
<td><code style="background: #f1f5f9; padding: 4px 8px; border-radius: 4px; font-weight: 600;">${item.code}</code></td>
|
|
<td>${item.used_at ? item.used_at : '-'}</td>
|
|
<td>
|
|
<div class="action-buttons">
|
|
<button class="action-btn delete-btn" onclick="deleteCode('${item.code}')" title="Delete Code">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
tbody.innerHTML += row;
|
|
});
|
|
|
|
console.log('Showing search results, hiding no results message'); // Debug
|
|
noResultsInfo.classList.remove('show');
|
|
searchResultInfo.classList.add('show');
|
|
searchResultText.textContent =
|
|
results.length === 1
|
|
? `Found coupon code: ${results[0].code}`
|
|
: `Found ${results.length} coupon codes matching "${query}"`;
|
|
|
|
} catch (err) {
|
|
console.error("Search failed:", err);
|
|
noResultsInfo.classList.add('show');
|
|
searchResultInfo.classList.remove('show');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shows all coupon codes by clearing search filters
|
|
* Restores the normal paginated view
|
|
*/
|
|
function showAllCoupons() {
|
|
console.log('showAllCoupons called, currentPage:', currentPage); // Debug
|
|
|
|
const searchResultInfo = document.getElementById('searchResultInfo');
|
|
const noResultsInfo = document.getElementById('noResultsInfo');
|
|
|
|
// Hide info messages
|
|
searchResultInfo.classList.remove('show');
|
|
noResultsInfo.classList.remove('show');
|
|
|
|
// Reload the full coupon list to current page
|
|
loadCodeList(currentPage);
|
|
}
|
|
|
|
/**
|
|
* Clears the search input and shows all coupons
|
|
*/
|
|
function clearSearch() {
|
|
const searchInput = document.getElementById('searchInput');
|
|
const searchClear = document.getElementById('searchClear');
|
|
|
|
searchInput.value = '';
|
|
searchClear.classList.remove('show');
|
|
showAllCoupons();
|
|
}
|
|
|
|
/**
|
|
* Changes the current page for pagination
|
|
*/
|
|
function changePage(direction) {
|
|
const newPage = currentPage + direction;
|
|
if (newPage >= 1 && newPage <= totalPages) {
|
|
loadCodeList(newPage);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Navigates directly to a specific page
|
|
*/
|
|
function goToPage(page) {
|
|
if (page >= 1 && page <= totalPages && page !== currentPage) {
|
|
loadCodeList(page);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the pagination information display
|
|
*/
|
|
function updatePaginationInfo() {
|
|
const start = totalCodes === 0 ? 0 : (currentPage - 1) * codesPerPage + 1;
|
|
const end = Math.min(currentPage * codesPerPage, totalCodes);
|
|
|
|
document.getElementById('paginationInfo').textContent =
|
|
`Showing ${start}-${end} of ${totalCodes} codes`;
|
|
}
|
|
|
|
/**
|
|
* Updates the pagination control buttons and page numbers
|
|
*/
|
|
function updatePaginationControls() {
|
|
const prevBtn = document.getElementById('prevPage');
|
|
const nextBtn = document.getElementById('nextPage');
|
|
const pageNumbers = document.getElementById('pageNumbers');
|
|
|
|
// Update prev/next buttons
|
|
prevBtn.disabled = currentPage <= 1;
|
|
nextBtn.disabled = currentPage >= totalPages;
|
|
|
|
// Generate page numbers
|
|
pageNumbers.innerHTML = '';
|
|
|
|
if (totalPages <= 7) {
|
|
// Show all pages if 7 or fewer
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
createPageButton(i, pageNumbers);
|
|
}
|
|
} else {
|
|
// Show first page
|
|
createPageButton(1, pageNumbers);
|
|
|
|
if (currentPage > 4) {
|
|
pageNumbers.innerHTML += '<span class="page-ellipsis">...</span>';
|
|
}
|
|
|
|
// Show pages around current page
|
|
const start = Math.max(2, currentPage - 1);
|
|
const end = Math.min(totalPages - 1, currentPage + 1);
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
createPageButton(i, pageNumbers);
|
|
}
|
|
|
|
if (currentPage < totalPages - 3) {
|
|
pageNumbers.innerHTML += '<span class="page-ellipsis">...</span>';
|
|
}
|
|
|
|
// Show last page
|
|
if (totalPages > 1) {
|
|
createPageButton(totalPages, pageNumbers);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a page button for pagination controls
|
|
*/
|
|
function createPageButton(pageNum, container) {
|
|
const button = document.createElement('button');
|
|
button.className = `page-number ${pageNum === currentPage ? 'active' : ''}`;
|
|
button.textContent = pageNum;
|
|
button.onclick = () => goToPage(pageNum);
|
|
container.appendChild(button);
|
|
}
|
|
|
|
// Upload functionality
|
|
let previewData = [];
|
|
let currentDeleteCode = null;
|
|
|
|
/**
|
|
* Handles file selection for Excel upload
|
|
* Validates file type and size, then processes the file
|
|
*/
|
|
function handleFileSelect(event) {
|
|
console.log("File select triggered"); // Debug
|
|
const file = event.target.files[0];
|
|
if (!file) {
|
|
console.log("No file selected"); // Debug
|
|
return;
|
|
}
|
|
|
|
console.log("File selected:", file.name, file.size); // Debug
|
|
|
|
const fileInfo = document.getElementById('fileInfo');
|
|
const fileName = document.getElementById('fileName');
|
|
const validationErrors = document.getElementById('validationErrors');
|
|
const previewSection = document.getElementById('previewSection');
|
|
const uploadResult = document.getElementById('uploadResult');
|
|
|
|
// Reset previous states
|
|
validationErrors.style.display = 'none';
|
|
previewSection.style.display = 'none';
|
|
uploadResult.innerHTML = '';
|
|
|
|
// Validate file type
|
|
if (!file.name.match(/\.(xlsx|xls)$/i)) {
|
|
console.log("Invalid file type"); // Debug
|
|
showValidationErrors(['Please select a valid Excel file (.xlsx or .xls)']);
|
|
return;
|
|
}
|
|
|
|
// Validate file size (10MB limit)
|
|
if (file.size > 10 * 1024 * 1024) {
|
|
console.log("File too large"); // Debug
|
|
showValidationErrors(['File size must be less than 10MB']);
|
|
return;
|
|
}
|
|
|
|
// Show file info
|
|
fileName.textContent = file.name;
|
|
fileInfo.style.display = 'flex';
|
|
console.log("File info displayed"); // Debug
|
|
|
|
// Read and process file
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
console.log("File read complete, processing..."); // Debug
|
|
try {
|
|
const data = new Uint8Array(e.target.result);
|
|
const workbook = XLSX.read(data, { type: 'array' });
|
|
console.log("Workbook read:", workbook.SheetNames); // Debug
|
|
|
|
// Get first worksheet
|
|
const worksheetName = workbook.SheetNames[0];
|
|
const worksheet = workbook.Sheets[worksheetName];
|
|
|
|
// Convert to JSON
|
|
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
|
console.log("JSON data:", jsonData); // Debug
|
|
|
|
if (jsonData.length < 2) {
|
|
console.log("Not enough data rows"); // Debug
|
|
showValidationErrors(['Excel file must contain at least a header row and one data row']);
|
|
return;
|
|
}
|
|
|
|
// Process data
|
|
processExcelData(jsonData);
|
|
|
|
} catch (error) {
|
|
console.error('Error reading file:', error);
|
|
showValidationErrors(['Error reading Excel file. Please check file format.']);
|
|
}
|
|
};
|
|
|
|
reader.onerror = function(error) {
|
|
console.error('FileReader error:', error); // Debug
|
|
};
|
|
|
|
console.log("Starting file read..."); // Debug
|
|
reader.readAsArrayBuffer(file);
|
|
}
|
|
|
|
/**
|
|
* Processes Excel data and validates coupon codes
|
|
* Detects column headers and validates data format
|
|
*/
|
|
function processExcelData(data) {
|
|
const errors = [];
|
|
const processed = [];
|
|
const duplicates = new Set();
|
|
const seen = new Set();
|
|
|
|
// Expected headers (case insensitive)
|
|
const headers = data[0].map(h => String(h).toLowerCase().trim());
|
|
|
|
// Column index detection
|
|
const codeIndex = headers.findIndex(h => h.includes('code'));
|
|
const usageIndex = headers.findIndex(h => h.includes('usage') || h.includes('use'));
|
|
|
|
// Check if code column exists
|
|
if (codeIndex === -1) {
|
|
errors.push(`Missing required column: code`);
|
|
}
|
|
|
|
if (errors.length === 0) {
|
|
// Process data rows
|
|
for (let i = 1; i < data.length; i++) {
|
|
const row = data[i];
|
|
|
|
if (!row || row.length === 0) continue; // Skip empty rows
|
|
|
|
const code = String(row[codeIndex] || '').trim().toUpperCase();
|
|
let usage = 0;
|
|
|
|
// Handle usage column presence and validity
|
|
if (usageIndex !== -1) {
|
|
const rawUsage = row[usageIndex];
|
|
usage = isNaN(rawUsage) || rawUsage === null || rawUsage === "" ? 0 : parseInt(rawUsage);
|
|
}
|
|
|
|
// Validate code
|
|
if (!code) {
|
|
errors.push(`Row ${i + 1}: Code is required`);
|
|
continue;
|
|
}
|
|
|
|
if (code.length < 3) {
|
|
errors.push(`Row ${i + 1}: Code must be at least 3 characters`);
|
|
continue;
|
|
}
|
|
|
|
// Check for duplicates within file
|
|
if (seen.has(code)) {
|
|
duplicates.add(code);
|
|
continue;
|
|
}
|
|
seen.add(code);
|
|
|
|
// Validate usage count
|
|
if (usage < 0) {
|
|
errors.push(`Row ${i + 1}: Usage count cannot be negative`);
|
|
continue;
|
|
}
|
|
|
|
processed.push({
|
|
code,
|
|
usage
|
|
});
|
|
}
|
|
}
|
|
|
|
// Show results
|
|
if (errors.length > 0) {
|
|
showValidationErrors(errors);
|
|
}
|
|
|
|
if (processed.length > 0) {
|
|
previewData = processed;
|
|
showPreview(processed, duplicates.size);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Displays validation errors for Excel file processing
|
|
*/
|
|
function showValidationErrors(errors) {
|
|
const validationErrors = document.getElementById('validationErrors');
|
|
const errorList = document.getElementById('errorList');
|
|
|
|
errorList.innerHTML = '';
|
|
errors.forEach(error => {
|
|
const li = document.createElement('li');
|
|
li.textContent = error;
|
|
errorList.appendChild(li);
|
|
});
|
|
|
|
validationErrors.style.display = 'block';
|
|
}
|
|
|
|
/**
|
|
* Shows preview of processed Excel data before upload
|
|
* Displays statistics and sample rows
|
|
*/
|
|
function showPreview(data, duplicateCount) {
|
|
const previewSection = document.getElementById('previewSection');
|
|
const previewTableBody = document.getElementById('previewTableBody');
|
|
|
|
// Update stats
|
|
document.getElementById('totalCodes').textContent = data.length + duplicateCount;
|
|
document.getElementById('validCodes').textContent = data.length;
|
|
document.getElementById('duplicateCodes').textContent = duplicateCount;
|
|
|
|
// Show preview table (first 10 rows) - Fixed to match 2-column structure
|
|
previewTableBody.innerHTML = '';
|
|
const previewRows = data.slice(0, 10);
|
|
|
|
previewRows.forEach(item => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td><code style="background: #f1f5f9; padding: 4px 8px; border-radius: 4px; font-weight: 600;">${item.code}</code></td>
|
|
<td>${item.usage}</td>
|
|
`;
|
|
previewTableBody.appendChild(row);
|
|
});
|
|
|
|
if (data.length > 10) {
|
|
const moreRow = document.createElement('tr');
|
|
moreRow.innerHTML = `
|
|
<td colspan="2" style="text-align: center; color: #6b7280; font-style: italic; padding: 20px;">
|
|
... and ${data.length - 10} more codes
|
|
</td>
|
|
`;
|
|
previewTableBody.appendChild(moreRow);
|
|
}
|
|
|
|
previewSection.style.display = 'block';
|
|
|
|
// Auto-upload to database
|
|
uploadCodes();
|
|
}
|
|
|
|
/**
|
|
* Uploads processed coupon codes to the database
|
|
* Makes API call with the validated coupon data
|
|
*/
|
|
async function uploadCodes() {
|
|
if (previewData.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const uploadBtn = document.getElementById('uploadBtn');
|
|
if (uploadBtn) uploadBtn.style.display = 'none';
|
|
const uploadResult = document.getElementById('uploadResult');
|
|
|
|
try {
|
|
const response = await fetch('/upload-codes', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ codes: previewData })
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok) {
|
|
uploadResult.innerHTML = `
|
|
<div class="result-card success">
|
|
<i class="fas fa-check-circle"></i>
|
|
<strong>Success!</strong> Successfully uploaded ${result.uploaded} coupon codes to database.
|
|
${result.skipped > 0 ? `<br><small>${result.skipped} codes were skipped (already exist).</small>` : ''}
|
|
</div>
|
|
`;
|
|
|
|
// Clear form after successful upload
|
|
setTimeout(() => {
|
|
clearFile();
|
|
}, 3000);
|
|
|
|
} else {
|
|
uploadResult.innerHTML = `
|
|
<div class="result-card error">
|
|
<i class="fas fa-exclamation-circle"></i>
|
|
<strong>Error!</strong> ${result.error || 'Failed to upload codes. Please try again.'}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Upload error:', error);
|
|
uploadResult.innerHTML = `
|
|
<div class="result-card error">
|
|
<i class="fas fa-exclamation-circle"></i>
|
|
<strong>Error!</strong> Network error. Please check your connection and try again.
|
|
</div>
|
|
`;
|
|
} finally {
|
|
// Reset button
|
|
uploadBtn.classList.remove('loading');
|
|
uploadBtn.innerHTML = '<i class="fas fa-upload"></i> Upload to Database';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clears the file upload form and resets all related UI elements
|
|
*/
|
|
function clearFile() {
|
|
const fileInput = document.getElementById('excelFile');
|
|
const fileInfo = document.getElementById('fileInfo');
|
|
const validationErrors = document.getElementById('validationErrors');
|
|
const previewSection = document.getElementById('previewSection');
|
|
const uploadResult = document.getElementById('uploadResult');
|
|
|
|
fileInput.value = '';
|
|
fileInfo.style.display = 'none';
|
|
validationErrors.style.display = 'none';
|
|
previewSection.style.display = 'none';
|
|
uploadResult.innerHTML = '';
|
|
previewData = [];
|
|
}
|
|
|
|
/**
|
|
* Sets up drag and drop functionality for file upload area
|
|
*/
|
|
function setupDragAndDrop() {
|
|
const uploadArea = document.querySelector('.file-upload-area');
|
|
|
|
uploadArea.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
uploadArea.classList.add('dragover');
|
|
});
|
|
|
|
uploadArea.addEventListener('dragleave', (e) => {
|
|
e.preventDefault();
|
|
uploadArea.classList.remove('dragover');
|
|
});
|
|
|
|
uploadArea.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
uploadArea.classList.remove('dragover');
|
|
|
|
const files = e.dataTransfer.files;
|
|
if (files.length > 0) {
|
|
const fileInput = document.getElementById('excelFile');
|
|
fileInput.files = files;
|
|
handleFileSelect({ target: { files } });
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handles radio button styling changes for generation mode selection
|
|
*/
|
|
function handleRadioChange() {
|
|
const radios = document.querySelectorAll('input[name="genMode"]');
|
|
const bulkCountWrapper = document.getElementById('bulkCountWrapper');
|
|
|
|
radios.forEach(radio => {
|
|
const option = radio.closest('.radio-option');
|
|
if (radio.checked) {
|
|
option.classList.add('active');
|
|
|
|
// Show/hide bulk count input
|
|
if (radio.value === 'bulk') {
|
|
bulkCountWrapper.classList.remove('hidden');
|
|
} else {
|
|
bulkCountWrapper.classList.add('hidden');
|
|
}
|
|
} else {
|
|
option.classList.remove('active');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Renders the translation upload section based on current file status
|
|
* Checks if translation file exists and displays appropriate UI
|
|
*/
|
|
async function renderTranslationUploadSection() {
|
|
const section = document.getElementById('translationUploadSection');
|
|
section.innerHTML = '<div style="text-align: center; padding: 40px; color: #2563eb;"><i class="fas fa-spinner fa-spin"></i> Checking translation file status...</div>';
|
|
|
|
let fileExists = false;
|
|
let fileName = '';
|
|
try {
|
|
const res = await fetch('/translations/status');
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
fileExists = data.file_exists;
|
|
fileName = data.file_name || '';
|
|
console.log('Translation status:', data); // Debug log
|
|
} else {
|
|
console.error('Status check failed:', res.status);
|
|
fileExists = false;
|
|
}
|
|
} catch (e) {
|
|
console.error('Error checking translation status:', e);
|
|
fileExists = false;
|
|
}
|
|
|
|
if (fileExists) {
|
|
section.innerHTML = `
|
|
<div style="display: flex; justify-content: center; align-items: center; min-height: 400px;">
|
|
<div style="max-width: 500px; width: 100%; text-align: center;">
|
|
<div style="margin-bottom: 30px; padding: 25px; background: #f0f9ff; border: 2px solid #0ea5e9; border-radius: 12px; color: #0c4a6e;">
|
|
<i class="fas fa-file-excel" style="font-size: 48px; color: #0ea5e9; margin-bottom: 15px;"></i>
|
|
<div style="font-weight: 700; font-size: 18px; margin-bottom: 8px;">Translation file is present</div>
|
|
${fileName ? `<div style="font-size: 14px; color: #0369a1; margin-bottom: 15px; background: #e0f2fe; padding: 8px 12px; border-radius: 6px; display: inline-block;"><i class="fas fa-file"></i> <strong>${fileName}</strong></div>` : ''}
|
|
<div style="font-size: 14px; color: #0369a1;">The extension will use this file for translations</div>
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 15px; justify-content: center; flex-wrap: wrap;">
|
|
<button class="btn btn-success" onclick="downloadTranslationFile()" style="padding: 15px 30px; font-size: 16px; font-weight: 600;">
|
|
<i class="fas fa-download"></i> Download Translation File
|
|
</button>
|
|
<button class="btn btn-danger" onclick="deleteTranslationFile()" style="padding: 15px 30px; font-size: 16px; font-weight: 600;">
|
|
<i class="fas fa-trash"></i> Delete Translation File
|
|
</button>
|
|
</div>
|
|
|
|
<div id="translationUploadResult" style="margin-top: 20px;"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} else {
|
|
section.innerHTML = `
|
|
<div style="display: flex; justify-content: center; align-items: center; min-height: 400px;">
|
|
<div style="max-width: 500px; width: 100%; text-align: center;">
|
|
<div style="background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">
|
|
<div class="form-group" style="margin-bottom: 20px;">
|
|
<label for="translationFile" class="form-label" style="text-align: left; display: block; margin-bottom: 12px; font-weight: 600; color: #374151;">Select Excel File (.xlsx or .xls)</label>
|
|
|
|
<div style="position: relative; border: 2px dashed #e2e8f0; border-radius: 8px; padding: 20px; background: #fafafa; transition: all 0.3s ease; cursor: pointer;"
|
|
onmouseover="this.style.borderColor='#3b82f6'; this.style.background='#f0f9ff';"
|
|
onmouseout="this.style.borderColor='#e2e8f0'; this.style.background='#fafafa';"
|
|
onclick="triggerFileInput();">
|
|
<input type="file" id="translationFile" accept=".xlsx,.xls" style="position: absolute; opacity: 0; width: 100%; height: 100%; cursor: pointer; pointer-events: none;" onchange="handleTranslationFileSelect(event)" />
|
|
<div style="text-align: center;">
|
|
<i class="fas fa-file-excel" style="font-size: 32px; color: #64748b; margin-bottom: 10px;"></i>
|
|
<div style="font-weight: 600; color: #475569; margin-bottom: 5px;">Click to select file</div>
|
|
<div style="font-size: 12px; color: #64748b;">or drag and drop here</div>
|
|
<div id="selectedFileName" style="margin-top: 10px; font-weight: 500; color: #059669;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="btn btn-primary" onclick="uploadTranslationFile()" style="width: 100%; padding: 15px; font-size: 16px; font-weight: 600;" id="uploadTranslationBtn">
|
|
<i class="fas fa-upload"></i> Upload Translation File
|
|
</button>
|
|
</div>
|
|
|
|
<div id="translationUploadResult" style="margin-top: 20px;"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggers the hidden file input element for translation file selection
|
|
*/
|
|
function triggerFileInput() {
|
|
document.getElementById('translationFile').click();
|
|
}
|
|
|
|
/**
|
|
* Handles translation file selection and displays selected filename
|
|
*/
|
|
function handleTranslationFileSelect(event) {
|
|
const file = event.target.files[0];
|
|
const fileNameDiv = document.getElementById('selectedFileName');
|
|
if (file) {
|
|
fileNameDiv.innerHTML = `<i class="fas fa-check-circle"></i> Selected: ${file.name}`;
|
|
fileNameDiv.style.color = '#059669';
|
|
} else {
|
|
fileNameDiv.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Uploads translation file to server
|
|
* Validates file selection and handles upload process
|
|
*/
|
|
async function uploadTranslationFile() {
|
|
const fileInput = document.getElementById('translationFile');
|
|
const resultDiv = document.getElementById('translationUploadResult');
|
|
const uploadBtn = document.getElementById('uploadTranslationBtn');
|
|
|
|
if (!fileInput || !fileInput.files.length) {
|
|
resultDiv.innerHTML = '<div style="color: #ef4444; background: #fef2f2; padding: 12px; border-radius: 8px; border: 1px solid #fecaca;"><i class="fas fa-exclamation-circle"></i> Please select a file to upload.</div>';
|
|
return;
|
|
}
|
|
|
|
const file = fileInput.files[0];
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
// Show loading state
|
|
if (uploadBtn) {
|
|
uploadBtn.classList.add('loading');
|
|
uploadBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Uploading...';
|
|
uploadBtn.disabled = true;
|
|
}
|
|
|
|
resultDiv.innerHTML = '<div style="color: #2563eb; background: #eff6ff; padding: 12px; border-radius: 8px; border: 1px solid #bfdbfe;"><i class="fas fa-spinner fa-spin"></i> Uploading...</div>';
|
|
|
|
try {
|
|
const response = await fetch('/upload-translations', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
resultDiv.innerHTML = `<div style="color: #059669; background: #ecfdf5; padding: 12px; border-radius: 8px; border: 1px solid #a7f3d0;"><i class="fas fa-check-circle"></i> ${data.message || 'Upload successful!'} - File: <strong>${file.name}</strong></div>`;
|
|
|
|
// Wait a moment before refreshing the section to show the success message
|
|
setTimeout(async () => {
|
|
await renderTranslationUploadSection();
|
|
}, 2000);
|
|
|
|
} else {
|
|
resultDiv.innerHTML = `<div style="color: #ef4444; background: #fef2f2; padding: 12px; border-radius: 8px; border: 1px solid #fecaca;"><i class="fas fa-exclamation-circle"></i> Upload failed: ${data.detail || 'Unknown error'}</div>`;
|
|
}
|
|
} catch (err) {
|
|
console.error('Upload error:', err);
|
|
resultDiv.innerHTML = '<div style="color: #ef4444; background: #fef2f2; padding: 12px; border-radius: 8px; border: 1px solid #fecaca;"><i class="fas fa-exclamation-circle"></i> Upload failed. Please try again.</div>';
|
|
} finally {
|
|
// Reset button
|
|
if (uploadBtn) {
|
|
uploadBtn.classList.remove('loading');
|
|
uploadBtn.innerHTML = '<i class="fas fa-upload"></i> Upload Translation File';
|
|
uploadBtn.disabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Downloads the current translation file from server
|
|
* Handles file download and user feedback
|
|
*/
|
|
async function downloadTranslationFile() {
|
|
const resultDiv = document.getElementById('translationUploadResult');
|
|
|
|
try {
|
|
// Show loading state
|
|
resultDiv.innerHTML = '<div style="color: #2563eb; background: #eff6ff; padding: 12px; border-radius: 8px; border: 1px solid #bfdbfe;"><i class="fas fa-spinner fa-spin"></i> Preparing download...</div>';
|
|
|
|
const response = await fetch('/download-translation', {
|
|
method: 'GET'
|
|
});
|
|
|
|
if (response.ok) {
|
|
// Get the filename from response headers or use default
|
|
const contentDisposition = response.headers.get('Content-Disposition');
|
|
let filename = 'translation.xlsx';
|
|
if (contentDisposition) {
|
|
const filenameMatch = contentDisposition.match(/filename="?([^"]+)"?/);
|
|
if (filenameMatch) {
|
|
filename = filenameMatch[1];
|
|
}
|
|
}
|
|
|
|
// Create blob and download
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.style.display = 'none';
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
|
|
resultDiv.innerHTML = `<div style="color: #059669; background: #ecfdf5; padding: 12px; border-radius: 8px; border: 1px solid #a7f3d0;"><i class="fas fa-check-circle"></i> Download started successfully!</div>`;
|
|
|
|
// Clear success message after 3 seconds
|
|
setTimeout(() => {
|
|
resultDiv.innerHTML = '';
|
|
}, 3000);
|
|
|
|
} else {
|
|
const data = await response.json();
|
|
resultDiv.innerHTML = `<div style="color: #ef4444; background: #fef2f2; padding: 12px; border-radius: 8px; border: 1px solid #fecaca;"><i class="fas fa-exclamation-circle"></i> Download failed: ${data.detail || 'File not found'}</div>`;
|
|
}
|
|
} catch (err) {
|
|
console.error('Download error:', err);
|
|
resultDiv.innerHTML = '<div style="color: #ef4444; background: #fef2f2; padding: 12px; border-radius: 8px; border: 1px solid #fecaca;"><i class="fas fa-exclamation-circle"></i> Download failed. Please try again.</div>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes the current translation file from server
|
|
* Shows confirmation dialog and handles deletion process
|
|
*/
|
|
async function deleteTranslationFile() {
|
|
const resultDiv = document.getElementById('translationUploadResult');
|
|
|
|
// Show confirmation
|
|
if (!confirm('Are you sure you want to delete the translation file? This action cannot be undone.')) {
|
|
return;
|
|
}
|
|
|
|
resultDiv.innerHTML = '<div style="color: #2563eb; background: #eff6ff; padding: 12px; border-radius: 8px; border: 1px solid #bfdbfe;"><i class="fas fa-spinner fa-spin"></i> Deleting...</div>';
|
|
|
|
try {
|
|
const response = await fetch('/delete-translation', {
|
|
method: 'DELETE'
|
|
});
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
resultDiv.innerHTML = `<div style="color: #059669; background: #ecfdf5; padding: 12px; border-radius: 8px; border: 1px solid #a7f3d0;"><i class="fas fa-check-circle"></i> ${data.message || 'Deleted successfully!'}</div>`;
|
|
|
|
// Wait a moment before refreshing the section to show the success message
|
|
setTimeout(async () => {
|
|
await renderTranslationUploadSection();
|
|
}, 2000);
|
|
|
|
} else {
|
|
resultDiv.innerHTML = `<div style="color: #ef4444; background: #fef2f2; padding: 12px; border-radius: 8px; border: 1px solid #fecaca;"><i class="fas fa-exclamation-circle"></i> Delete failed: ${data.detail || 'Unknown error'}</div>`;
|
|
}
|
|
} catch (err) {
|
|
console.error('Delete error:', err);
|
|
resultDiv.innerHTML = '<div style="color: #ef4444; background: #fef2f2; padding: 12px; border-radius: 8px; border: 1px solid #fecaca;"><i class="fas fa-exclamation-circle"></i> Delete failed. Please try again.</div>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes the dashboard when DOM content is loaded
|
|
* Sets up event listeners, initial states, and functionality
|
|
*/
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Set initial active states
|
|
const firstTab = document.querySelector('.tab');
|
|
if (firstTab) {
|
|
showTab('generate', { target: firstTab });
|
|
}
|
|
|
|
// Add event listeners to radio buttons
|
|
const radios = document.querySelectorAll('input[name="genMode"]');
|
|
radios.forEach(radio => {
|
|
radio.addEventListener('change', handleRadioChange);
|
|
});
|
|
|
|
// Initialize radio states
|
|
handleRadioChange();
|
|
|
|
// Add click handlers to radio options for better UX
|
|
document.querySelectorAll('.radio-option').forEach(option => {
|
|
option.addEventListener('click', function() {
|
|
const radio = this.querySelector('input[type="radio"]');
|
|
if (radio) {
|
|
radio.checked = true;
|
|
handleRadioChange();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add upload mode toggle handlers
|
|
const uploadRadios = document.querySelectorAll('input[name="uploadMode"]');
|
|
uploadRadios.forEach(radio => {
|
|
radio.addEventListener('change', function() {
|
|
toggleUploadMode(this.value);
|
|
});
|
|
});
|
|
|
|
// Add click handlers to upload radio options
|
|
document.querySelectorAll('.radio-option').forEach(option => {
|
|
option.addEventListener('click', function() {
|
|
const radioInput = this.querySelector('input[type="radio"]');
|
|
if (radioInput && radioInput.name === 'uploadMode') {
|
|
radioInput.checked = true;
|
|
toggleUploadMode(radioInput.value);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Initialize upload mode
|
|
toggleUploadMode("single");
|
|
|
|
// Setup drag and drop for file upload
|
|
setupDragAndDrop();
|
|
});
|
|
|
|
/**
|
|
* Sets up search functionality for coupon code filtering
|
|
* Adds event listeners and handles search input changes
|
|
*/
|
|
function setupSearchFunctionality() {
|
|
console.log('Setting up search functionality...'); // Debug
|
|
|
|
const searchInput = document.getElementById('searchInput');
|
|
const searchClear = document.getElementById('searchClear');
|
|
|
|
console.log('Search input found:', searchInput); // Debug
|
|
console.log('Search clear found:', searchClear); // Debug
|
|
|
|
if (searchInput && searchClear) {
|
|
// Remove existing event listeners to prevent duplicates
|
|
searchInput.removeEventListener('input', searchInput.searchHandler);
|
|
|
|
// Create the event handler function
|
|
searchInput.searchHandler = function() {
|
|
const query = this.value.trim();
|
|
console.log('Search query:', query); // Debug
|
|
|
|
if (query) {
|
|
searchClear.classList.add('show');
|
|
console.log('Calling filterCoupons with:', query); // Debug
|
|
filterCoupons(query);
|
|
} else {
|
|
searchClear.classList.remove('show');
|
|
console.log('Calling showAllCoupons'); // Debug
|
|
showAllCoupons();
|
|
}
|
|
};
|
|
|
|
// Add the event listener
|
|
searchInput.addEventListener('input', searchInput.searchHandler);
|
|
console.log('Search event listener added successfully'); // Debug
|
|
} else {
|
|
console.error('Search elements not found!'); // Debug
|
|
}
|
|
} |