remove old import
This commit is contained in:
@@ -141,10 +141,6 @@
|
||||
<i class="fa-solid fa-print"></i>
|
||||
<span>Printers</span>
|
||||
</button>
|
||||
<button class="flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 border-transparent hover:border-primary-300 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-all duration-200" id="import-tab" data-tab-target="#import" type="button" role="tab">
|
||||
<i class="fa-solid fa-file-import"></i>
|
||||
<span>Import</span>
|
||||
</button>
|
||||
<button class="flex items-center gap-2 px-6 py-4 text-sm font-medium border-b-2 border-transparent hover:border-primary-300 text-neutral-600 dark:text-neutral-400 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-neutral-50 dark:hover:bg-neutral-700/50 transition-all duration-200" id="issues-tab" data-tab-target="#issues" type="button" role="tab">
|
||||
<i class="fa-solid fa-bug"></i>
|
||||
<span>Issues</span>
|
||||
@@ -621,139 +617,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Import Tab -->
|
||||
<div id="import" role="tabpanel" class="hidden">
|
||||
<div class="flex flex-wrap -mx-4">
|
||||
<div class="w-full px-4">
|
||||
<h4 class="mb-4 text-xl font-semibold"><i class="fa-solid fa-upload"></i> Data Import Management</h4>
|
||||
|
||||
<!-- Import Status Panel -->
|
||||
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow mb-4">
|
||||
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700 flex items-center justify-between">
|
||||
<h5 class="m-0 font-semibold"><i class="fa-solid fa-circle-info"></i> Current Database Status</h5>
|
||||
<button class="px-3 py-1.5 border border-info-600 text-info-700 dark:text-info-200 rounded text-sm hover:bg-info-50 dark:hover:bg-info-900/20" onclick="loadImportStatus()">
|
||||
<i class="fa-solid fa-rotate-right"></i> Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div id="importStatus">
|
||||
<div class="text-center">
|
||||
<div class="inline-block w-6 h-6 border-2 border-neutral-300 border-t-primary-600 rounded-full animate-spin"></div>
|
||||
<p class="mt-2">Loading import status...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CSV File Upload Panel -->
|
||||
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow mb-4">
|
||||
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700">
|
||||
<h5 class="m-0 font-semibold"><i class="fa-regular fa-file-arrow-up"></i> Upload CSV Files</h5>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<form id="adminImportForm" enctype="multipart/form-data">
|
||||
<div class="flex flex-wrap -mx-3">
|
||||
<div class="w-full md:w-1/3 px-3 mb-3">
|
||||
<label for="adminFileType" class="block text-sm font-medium mb-1">Data Type *</label>
|
||||
<select class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent" id="adminFileType" name="fileType" required>
|
||||
<option value="">Select data type...</option>
|
||||
</select>
|
||||
<div class="text-sm text-neutral-500 dark:text-neutral-400 mt-1" id="adminFileTypeDescription"></div>
|
||||
</div>
|
||||
<div class="w-full md:w-1/2 px-3 mb-3">
|
||||
<label for="adminCsvFile" class="block text-sm font-medium mb-1">CSV File *</label>
|
||||
<input type="file" class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent" id="adminCsvFile" name="csvFile" accept=".csv" required>
|
||||
<div class="text-sm text-neutral-500 dark:text-neutral-400 mt-1">Select the CSV file to import</div>
|
||||
</div>
|
||||
<div class="w-full md:w-1/6 px-3 mb-3">
|
||||
<label class="block text-sm font-medium mb-1"> </label>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input class="rounded border-neutral-300 text-primary-600 focus:ring-primary-500" type="checkbox" id="adminReplaceExisting" name="replaceExisting">
|
||||
<span>Replace existing data</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button type="button" class="px-4 py-2 border border-neutral-300 dark:border-neutral-600 rounded hover:bg-neutral-50 dark:hover:bg-neutral-700" onclick="validateAdminFile()">
|
||||
<i class="fa-regular fa-circle-check"></i> Validate File
|
||||
</button>
|
||||
<button type="submit" class="px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white rounded" id="adminImportBtn">
|
||||
<i class="fa-solid fa-upload"></i> Import Data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Validation Results Panel -->
|
||||
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow mb-4 hidden" id="adminValidationPanel">
|
||||
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700">
|
||||
<h5 class="m-0 font-semibold"><i class="fa-solid fa-clipboard-check"></i> File Validation Results</h5>
|
||||
</div>
|
||||
<div class="p-4" id="adminValidationResults">
|
||||
<!-- Validation results will be shown here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import Progress Panel -->
|
||||
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow mb-4 hidden" id="adminProgressPanel">
|
||||
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700">
|
||||
<h5 class="m-0 font-semibold"><i class="fa-solid fa-hourglass-half"></i> Import Progress</h5>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="w-full bg-neutral-100 dark:bg-neutral-700 rounded h-3 overflow-hidden mb-3">
|
||||
<div class="h-3 bg-primary-600 transition-all" style="width: 0%" id="adminProgressBar"></div>
|
||||
</div>
|
||||
<div id="adminProgressStatus">Ready to import...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import Results Panel -->
|
||||
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow mb-4 hidden" id="adminResultsPanel">
|
||||
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700">
|
||||
<h5 class="m-0 font-semibold"><i class="fa-solid fa-circle-check"></i> Import Results</h5>
|
||||
</div>
|
||||
<div class="p-4" id="adminImportResults">
|
||||
<!-- Import results will be shown here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Management Panel -->
|
||||
<div class="bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg shadow">
|
||||
<div class="px-4 py-3 border-b border-neutral-200 dark:border-neutral-700">
|
||||
<h5 class="m-0 font-semibold"><i class="fa-solid fa-database"></i> Data Management</h5>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap -mx-4">
|
||||
<div class="w-full md:w-1/2 px-4">
|
||||
<h6 class="font-semibold">Clear Table Data</h6>
|
||||
<p class="text-neutral-500 dark:text-neutral-400 text-sm">Remove all records from a specific table (cannot be undone)</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<select class="w-full px-3 py-2 bg-white dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent" id="adminClearTableType">
|
||||
<option value="">Select table to clear...</option>
|
||||
</select>
|
||||
<button class="px-3 py-2 bg-danger-600 hover:bg-danger-700 text-white rounded" onclick="clearAdminTable()">
|
||||
<i class="fa-solid fa-trash"></i> Clear Table
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full md:w-1/2 px-4 mt-4 md:mt-0">
|
||||
<h6 class="font-semibold">Quick Actions</h6>
|
||||
<div class="grid gap-2">
|
||||
<button class="px-3 py-2 border border-info-600 text-info-700 dark:text-info-200 rounded hover:bg-info-50 dark:hover:bg-info-900/20" onclick="viewImportLogs()">
|
||||
<i class="fa-regular fa-file-lines"></i> View Import Logs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backup Tab -->
|
||||
<div id="backup" role="tabpanel" class="hidden">
|
||||
@@ -1302,9 +1165,6 @@ function onTabShown(tabName) {
|
||||
if (tabName === 'issues') {
|
||||
loadIssues();
|
||||
loadIssueStats();
|
||||
} else if (tabName === 'import') {
|
||||
loadAvailableImportFiles();
|
||||
loadImportStatus();
|
||||
} else if (tabName === 'backup') {
|
||||
loadBackups();
|
||||
} else if (tabName === 'users') {
|
||||
@@ -2533,380 +2393,6 @@ async function addResponse() {
|
||||
}
|
||||
}
|
||||
|
||||
// Import Management Functions
|
||||
let availableImportFiles = {};
|
||||
let importInProgress = false;
|
||||
|
||||
async function loadAvailableImportFiles() {
|
||||
try {
|
||||
const response = await window.http.wrappedFetch('/api/import/available-files');
|
||||
|
||||
if (!response.ok) throw new Error('Failed to load available files');
|
||||
|
||||
const data = await response.json();
|
||||
availableImportFiles = data;
|
||||
|
||||
// Populate file type dropdowns
|
||||
const fileTypeSelect = document.getElementById('adminFileType');
|
||||
const clearTableSelect = document.getElementById('adminClearTableType');
|
||||
|
||||
fileTypeSelect.innerHTML = '<option value="">Select data type...</option>';
|
||||
clearTableSelect.innerHTML = '<option value="">Select table to clear...</option>';
|
||||
|
||||
data.available_files.forEach(fileType => {
|
||||
const description = data.descriptions[fileType] || fileType;
|
||||
|
||||
const option1 = document.createElement('option');
|
||||
option1.value = fileType;
|
||||
option1.textContent = `${fileType} - ${description}`;
|
||||
fileTypeSelect.appendChild(option1);
|
||||
|
||||
const option2 = document.createElement('option');
|
||||
option2.value = fileType;
|
||||
option2.textContent = `${fileType} - ${description}`;
|
||||
clearTableSelect.appendChild(option2);
|
||||
});
|
||||
|
||||
// Setup form listener
|
||||
document.getElementById('adminImportForm').addEventListener('submit', handleAdminImport);
|
||||
|
||||
// File type change listener
|
||||
document.getElementById('adminFileType').addEventListener('change', updateAdminFileTypeDescription);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading available files:', error);
|
||||
showAlert('Error loading available file types: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadImportStatus() {
|
||||
try {
|
||||
const response = await window.http.wrappedFetch('/api/import/status');
|
||||
|
||||
if (!response.ok) throw new Error('Failed to load import status');
|
||||
|
||||
const status = await response.json();
|
||||
displayImportStatus(status);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading import status:', error);
|
||||
document.getElementById('importStatus').innerHTML =
|
||||
`<div class="bg-danger-50 text-danger-800 border-l-4 border-danger-500 p-3 rounded">Error loading import status: ${error.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function displayImportStatus(status) {
|
||||
const container = document.getElementById('importStatus');
|
||||
|
||||
let html = '<div class="grid grid-cols-1 md:grid-cols-3 gap-2">';
|
||||
let totalRecords = 0;
|
||||
|
||||
Object.entries(status).forEach(([fileType, info], index) => {
|
||||
totalRecords += info.record_count || 0;
|
||||
|
||||
const statusClass = info.error ? 'danger' : (info.record_count > 0 ? 'success' : 'secondary');
|
||||
const statusIcon = info.error ? 'triangle-exclamation' : (info.record_count > 0 ? 'circle-check' : 'circle');
|
||||
|
||||
// grid handles wrapping; no manual row breaks needed
|
||||
|
||||
html += `
|
||||
<div>
|
||||
<div class="border rounded-lg p-2 ${statusClass === 'danger' ? 'border-danger-300 dark:border-danger-700' : statusClass === 'success' ? 'border-success-300 dark:border-success-700' : 'border-neutral-200 dark:border-neutral-700'}">
|
||||
<div class="p-0.5">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<small class="font-semibold">${fileType}</small><br>
|
||||
<small class="text-neutral-500 dark:text-neutral-400">${info.table_name}</small>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<i class="fa-solid fa-${statusIcon} ${statusClass === 'danger' ? 'text-danger-600' : statusClass === 'success' ? 'text-success-600' : 'text-neutral-400'}"></i><br>
|
||||
<small class="font-semibold">${info.record_count || 0}</small>
|
||||
</div>
|
||||
</div>
|
||||
${info.error ? `<div class="text-danger-600 dark:text-danger-400 text-xs mt-1">${info.error}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
html += `<div class="mt-3 text-center">
|
||||
<strong>Total Records: ${totalRecords.toLocaleString()}</strong>
|
||||
</div>`;
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function updateAdminFileTypeDescription() {
|
||||
const fileType = document.getElementById('adminFileType').value;
|
||||
const description = availableImportFiles.descriptions && availableImportFiles.descriptions[fileType];
|
||||
document.getElementById('adminFileTypeDescription').textContent = description || '';
|
||||
}
|
||||
|
||||
async function validateAdminFile() {
|
||||
const fileType = document.getElementById('adminFileType').value;
|
||||
const fileInput = document.getElementById('adminCsvFile');
|
||||
|
||||
if (!fileType || !fileInput.files[0]) {
|
||||
showAlert('Please select both file type and CSV file', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', fileInput.files[0]);
|
||||
|
||||
try {
|
||||
showAdminProgress(true, 'Validating file...');
|
||||
|
||||
const response = await window.http.wrappedFetch(`/api/import/validate/${fileType}`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw await window.http.toError(response, 'Validation failed');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
displayAdminValidationResults(result);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Validation error:', error);
|
||||
const message = window.http && typeof window.http.formatAlert === 'function'
|
||||
? window.http.formatAlert(error, 'Validation failed')
|
||||
: 'Validation failed: ' + (error && error.message ? error.message : String(error));
|
||||
showAlert(message, 'error');
|
||||
} finally {
|
||||
showAdminProgress(false);
|
||||
}
|
||||
}
|
||||
|
||||
function displayAdminValidationResults(result) {
|
||||
const panel = document.getElementById('adminValidationPanel');
|
||||
const container = document.getElementById('adminValidationResults');
|
||||
|
||||
let html = '';
|
||||
|
||||
// Overall status
|
||||
const statusClass = result.valid ? 'success' : 'danger';
|
||||
const statusIcon = result.valid ? 'circle-check' : 'triangle-exclamation';
|
||||
|
||||
html += `
|
||||
<div class="${statusClass === 'success' ? 'bg-success-50 text-success-700 border-success-500' : 'bg-danger-50 text-danger-700 border-danger-500'} border-l-4 p-3 rounded">
|
||||
<i class="fa-solid fa-${statusIcon}"></i>
|
||||
File validation ${result.valid ? 'passed' : 'failed'}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Headers validation
|
||||
html += '<h6>Column Headers</h6>';
|
||||
if (result.headers.missing.length > 0) {
|
||||
html += `<div class="bg-warning-50 text-warning-800 border-l-4 border-warning-500 p-3 rounded">
|
||||
<strong>Missing columns:</strong> ${result.headers.missing.join(', ')}
|
||||
</div>`;
|
||||
}
|
||||
if (result.headers.extra.length > 0) {
|
||||
html += `<div class="bg-info-50 text-info-800 border-l-4 border-info-500 p-3 rounded">
|
||||
<strong>Extra columns:</strong> ${result.headers.extra.join(', ')}
|
||||
</div>`;
|
||||
}
|
||||
if (result.headers.missing.length === 0 && result.headers.extra.length === 0) {
|
||||
html += '<div class="bg-success-50 text-success-800 border-l-4 border-success-500 p-3 rounded">All expected columns found</div>';
|
||||
}
|
||||
|
||||
// Sample data
|
||||
if (result.sample_data && result.sample_data.length > 0) {
|
||||
html += '<h6>Sample Data (First 10 rows)</h6>';
|
||||
html += '<div class="overflow-x-auto">';
|
||||
html += '<table class="min-w-full text-sm">';
|
||||
html += '<thead><tr>';
|
||||
Object.keys(result.sample_data[0]).forEach(header => {
|
||||
html += `<th class="px-2 py-1 text-left border-b border-neutral-200 dark:border-neutral-700">${header}</th>`;
|
||||
});
|
||||
html += '</tr></thead><tbody>';
|
||||
|
||||
result.sample_data.forEach(row => {
|
||||
html += '<tr class="odd:bg-neutral-50 dark:odd:bg-neutral-800/50">';
|
||||
Object.values(row).forEach(value => {
|
||||
html += `<td class="px-2 py-1">${value || ''}</td>`;
|
||||
});
|
||||
html += '</tr>';
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
}
|
||||
|
||||
// Validation errors
|
||||
if (result.validation_errors && result.validation_errors.length > 0) {
|
||||
html += '<h6>Data Issues Found</h6>';
|
||||
html += '<div class="bg-warning-50 text-warning-800 border-l-4 border-warning-500 p-3 rounded">';
|
||||
result.validation_errors.forEach(error => {
|
||||
html += `<div>Row ${error.row}, Field "${error.field}": ${error.error}</div>`;
|
||||
});
|
||||
if (result.total_errors > result.validation_errors.length) {
|
||||
html += `<div class="mt-2"><strong>... and ${result.total_errors - result.validation_errors.length} more errors</strong></div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
panel.style.display = 'block';
|
||||
}
|
||||
|
||||
async function handleAdminImport(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (importInProgress) {
|
||||
showAlert('Import already in progress', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const fileType = document.getElementById('adminFileType').value;
|
||||
const fileInput = document.getElementById('adminCsvFile');
|
||||
const replaceExisting = document.getElementById('adminReplaceExisting').checked;
|
||||
|
||||
if (!fileType || !fileInput.files[0]) {
|
||||
showAlert('Please select both file type and CSV file', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
importInProgress = true;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', fileInput.files[0]);
|
||||
formData.append('replace_existing', replaceExisting);
|
||||
|
||||
try {
|
||||
showAdminProgress(true, 'Importing data...');
|
||||
|
||||
const response = await window.http.wrappedFetch(`/api/import/upload/${fileType}`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw await window.http.toError(response, 'Import failed');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
displayAdminImportResults(result);
|
||||
|
||||
// Refresh status after successful import
|
||||
await loadImportStatus();
|
||||
|
||||
// Reset form
|
||||
document.getElementById('adminImportForm').reset();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Import error:', error);
|
||||
const message = window.http && typeof window.http.formatAlert === 'function'
|
||||
? window.http.formatAlert(error, 'Import failed')
|
||||
: 'Import failed: ' + (error && error.message ? error.message : String(error));
|
||||
showAlert(message, 'error');
|
||||
} finally {
|
||||
importInProgress = false;
|
||||
showAdminProgress(false);
|
||||
}
|
||||
}
|
||||
|
||||
function displayAdminImportResults(result) {
|
||||
const panel = document.getElementById('adminResultsPanel');
|
||||
const container = document.getElementById('adminImportResults');
|
||||
|
||||
const successClass = result.errors && result.errors.length > 0 ? 'warning' : 'success';
|
||||
|
||||
let html = `
|
||||
<div class="${successClass === 'warning' ? 'bg-warning-50 text-warning-800 border-warning-500' : 'bg-success-50 text-success-800 border-success-500'} border-l-4 p-3 rounded">
|
||||
<h6 class="font-semibold flex items-center gap-2"><i class="fa-regular fa-circle-check"></i> Import Completed</h6>
|
||||
<p class="mb-0">
|
||||
<strong>File Type:</strong> ${result.file_type}<br>
|
||||
<strong>Records Imported:</strong> ${result.imported_count}<br>
|
||||
<strong>Errors:</strong> ${result.total_errors || 0}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
html += '<h6>Import Errors</h6>';
|
||||
html += '<div class="bg-danger-50 text-danger-800 border-l-4 border-danger-500 p-3 rounded">';
|
||||
result.errors.forEach(error => {
|
||||
html += `<div><strong>Row ${error.row}:</strong> ${error.error}</div>`;
|
||||
});
|
||||
if (result.total_errors > result.errors.length) {
|
||||
html += `<div class="mt-2"><strong>... and ${result.total_errors - result.errors.length} more errors</strong></div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Add summary for printers
|
||||
if (result.file_type === 'PRINTERS.csv') {
|
||||
html += `
|
||||
<div class="mt-2 p-2 bg-neutral-50 dark:bg-neutral-800/50 rounded border border-neutral-200 dark:border-neutral-700 text-sm">
|
||||
<strong>Printers:</strong> ${result.created_count || 0} created, ${result.updated_count || 0} updated
|
||||
</div>
|
||||
`;
|
||||
// Auto-refresh printers tab list
|
||||
try { loadPrinters(); } catch (_) {}
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
panel.style.display = 'block';
|
||||
}
|
||||
|
||||
function showAdminProgress(show, message = '') {
|
||||
const panel = document.getElementById('adminProgressPanel');
|
||||
const status = document.getElementById('adminProgressStatus');
|
||||
const bar = document.getElementById('adminProgressBar');
|
||||
|
||||
if (show) {
|
||||
status.textContent = message;
|
||||
bar.style.width = '100%';
|
||||
bar.textContent = 'Processing...';
|
||||
panel.style.display = 'block';
|
||||
} else {
|
||||
panel.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function clearAdminTable() {
|
||||
const fileType = document.getElementById('adminClearTableType').value;
|
||||
|
||||
if (!fileType) {
|
||||
showAlert('Please select a table to clear', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`Are you sure you want to clear all data from ${fileType}? This action cannot be undone.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await window.http.wrappedFetch(`/api/import/clear/${fileType}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw await window.http.toError(response, 'Clear operation failed');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
showAlert(`Successfully cleared ${result.deleted_count} records from ${result.table_name}`, 'success');
|
||||
|
||||
// Refresh status
|
||||
await loadImportStatus();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Clear table error:', error);
|
||||
const message = window.http && typeof window.http.formatAlert === 'function'
|
||||
? window.http.formatAlert(error, 'Clear operation failed')
|
||||
: 'Clear operation failed: ' + (error && error.message ? error.message : String(error));
|
||||
showAlert(message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function viewImportLogs() {
|
||||
showAlert('Import logs functionality coming soon', 'info');
|
||||
}
|
||||
|
||||
function searchUsers() {
|
||||
const searchTerm = document.getElementById('user-search').value.toLowerCase();
|
||||
|
||||
Reference in New Issue
Block a user