changes
This commit is contained in:
@@ -56,6 +56,30 @@
|
||||
<i class="fa-solid fa-circle-question"></i>
|
||||
Help
|
||||
</button>
|
||||
<div class="hidden md:flex items-center gap-2 ml-2">
|
||||
<label class="text-sm font-medium text-neutral-700 dark:text-neutral-300">Templates:</label>
|
||||
<button type="button" id="downloadFilesTemplateBtn" class="px-2 py-1 text-xs border border-neutral-300 dark:border-neutral-600 rounded hover:bg-neutral-100 dark:hover:bg-neutral-700" title="Download FILES.csv template">
|
||||
FILES
|
||||
</button>
|
||||
<button type="button" id="downloadLedgerTemplateBtn" class="px-2 py-1 text-xs border border-neutral-300 dark:border-neutral-600 rounded hover:bg-neutral-100 dark:hover:bg-neutral-700" title="Download LEDGER.csv template">
|
||||
LEDGER
|
||||
</button>
|
||||
<button type="button" id="downloadPaymentsTemplateBtn" class="px-2 py-1 text-xs border border-neutral-300 dark:border-neutral-600 rounded hover:bg-neutral-100 dark:hover:bg-neutral-700" title="Download PAYMENTS.csv template">
|
||||
PAYMENTS
|
||||
</button>
|
||||
<button type="button" id="downloadRolodexTemplateBtn" class="px-2 py-1 text-xs border border-neutral-300 dark:border-neutral-600 rounded hover:bg-neutral-100 dark:hover:bg-neutral-700" title="Download ROLODEX.csv template">
|
||||
ROLODEX
|
||||
</button>
|
||||
<button type="button" id="downloadTrnsactnTemplateBtn" class="px-2 py-1 text-xs border border-neutral-300 dark:border-neutral-600 rounded hover:bg-neutral-100 dark:hover:bg-neutral-700" title="Download TRNSACTN.csv template">
|
||||
TRNSACTN
|
||||
</button>
|
||||
<button type="button" id="downloadDepositsTemplateBtn" class="px-2 py-1 text-xs border border-neutral-300 dark:border-neutral-600 rounded hover:bg-neutral-100 dark:hover:bg-neutral-700" title="Download DEPOSITS.csv template">
|
||||
DEPOSITS
|
||||
</button>
|
||||
<button type="button" id="downloadTemplatesBundleBtn" class="px-2 py-1 text-xs border border-neutral-300 dark:border-neutral-600 rounded hover:bg-neutral-100 dark:hover:bg-neutral-700" title="Download selected templates as ZIP">
|
||||
Download…
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -166,11 +190,15 @@
|
||||
<h5 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100 flex items-center gap-2">
|
||||
<i class="fa-solid fa-clipboard-check"></i>
|
||||
<span>File Validation Results</span>
|
||||
<button type="button" class="ml-auto text-xs underline text-neutral-600 dark:text-neutral-400" onclick="toggleHeaderHelp()">Required headers help</button>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="p-6" id="validationResults">
|
||||
<!-- Validation results will be shown here -->
|
||||
</div>
|
||||
<div class="px-6 pb-4 hidden" id="headerHelp">
|
||||
<!-- Content will be populated dynamically by toggleHeaderHelp() -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import Progress Panel -->
|
||||
@@ -273,6 +301,94 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Centralized required headers map with examples
|
||||
const CSV_REQUIRED_HEADERS = {
|
||||
'FILES.csv': {
|
||||
required: ['file_no', 'id', 'empl_num', 'file_type', 'opened', 'status', 'rate_per_hour'],
|
||||
example: 'File_No,Id,Empl_Num,File_Type,Opened,Status,Rate_Per_Hour\nF-001,CLIENT-1,EMP01,CIVIL,2024-01-01,ACTIVE,150'
|
||||
},
|
||||
'LEDGER.csv': {
|
||||
required: ['file_no', 'date', 'empl_num', 't_code', 't_type', 'amount'],
|
||||
example: 'File_No,Date,Empl_Num,T_Code,T_Type,Amount\nF-001,2024-01-15,EMP01,FEE,1,500.00'
|
||||
},
|
||||
'PAYMENTS.csv': {
|
||||
required: ['deposit_date', 'amount'],
|
||||
example: 'Deposit_Date,Amount\n2024-01-15,1500.00'
|
||||
},
|
||||
'TRNSACTN.csv': {
|
||||
required: ['file_no', 'date', 'empl_num', 't_code', 't_type', 'amount'],
|
||||
example: 'File_No,Date,Empl_Num,T_Code,T_Type,Amount\nF-002,2024-02-10,EMP02,FEE,1,250.00'
|
||||
},
|
||||
'DEPOSITS.csv': {
|
||||
required: ['deposit_date', 'total'],
|
||||
example: 'Deposit_Date,Total\n2024-02-10,1500.00'
|
||||
},
|
||||
'ROLODEX.csv': {
|
||||
required: ['id', 'last'],
|
||||
example: 'Id,Last,First,A1,City,Abrev,Zip,Email\nCLIENT-1,Smith,John,123 Main St,Denver,CO,80202,john.smith@example.com'
|
||||
}
|
||||
};
|
||||
|
||||
function getRequiredHeadersText(fileType) {
|
||||
const info = CSV_REQUIRED_HEADERS[fileType];
|
||||
return info ? info.required.join(', ') : 'varies';
|
||||
}
|
||||
|
||||
function getRequiredHeadersTooltip(fileType) {
|
||||
const info = CSV_REQUIRED_HEADERS[fileType];
|
||||
if (!info) return 'Required headers vary by file type';
|
||||
return `Required: ${info.required.join(', ')}\n\nExample:\n${info.example}`;
|
||||
}
|
||||
|
||||
function toggleHeaderHelp() {
|
||||
const el = document.getElementById('headerHelp');
|
||||
if (!el) return;
|
||||
|
||||
// Update content dynamically if not already populated
|
||||
if (!el.dataset.populated) {
|
||||
const content = `
|
||||
<div class="p-3 bg-neutral-50 dark:bg-neutral-900 rounded text-xs text-neutral-700 dark:text-neutral-300">
|
||||
<div class="font-semibold mb-2">Minimal required headers by CSV:</div>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<div class="font-mono font-semibold">FILES.csv</div>
|
||||
<div class="text-neutral-600 dark:text-neutral-400">Required: ${getRequiredHeadersText('FILES.csv')}</div>
|
||||
<pre class="font-mono text-xs mt-1 text-neutral-500 whitespace-pre-wrap">${CSV_REQUIRED_HEADERS['FILES.csv'].example}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-mono font-semibold">LEDGER.csv</div>
|
||||
<div class="text-neutral-600 dark:text-neutral-400">Required: ${getRequiredHeadersText('LEDGER.csv')}</div>
|
||||
<pre class="font-mono text-xs mt-1 text-neutral-500 whitespace-pre-wrap">${CSV_REQUIRED_HEADERS['LEDGER.csv'].example}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-mono font-semibold">PAYMENTS.csv</div>
|
||||
<div class="text-neutral-600 dark:text-neutral-400">Required: ${getRequiredHeadersText('PAYMENTS.csv')}</div>
|
||||
<pre class="font-mono text-xs mt-1 text-neutral-500 whitespace-pre-wrap">${CSV_REQUIRED_HEADERS['PAYMENTS.csv'].example}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-mono font-semibold">TRNSACTN.csv</div>
|
||||
<div class="text-neutral-600 dark:text-neutral-400">Required: ${getRequiredHeadersText('TRNSACTN.csv')}</div>
|
||||
<pre class="font-mono text-xs mt-1 text-neutral-500 whitespace-pre-wrap">${CSV_REQUIRED_HEADERS['TRNSACTN.csv'].example}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-mono font-semibold">DEPOSITS.csv</div>
|
||||
<div class="text-neutral-600 dark:text-neutral-400">Required: ${getRequiredHeadersText('DEPOSITS.csv')}</div>
|
||||
<pre class="font-mono text-xs mt-1 text-neutral-500 whitespace-pre-wrap">${CSV_REQUIRED_HEADERS['DEPOSITS.csv'].example}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-mono font-semibold">ROLODEX.csv</div>
|
||||
<div class="text-neutral-600 dark:text-neutral-400">Required: ${getRequiredHeadersText('ROLODEX.csv')}</div>
|
||||
<pre class="font-mono text-xs mt-1 text-neutral-500 whitespace-pre-wrap">${CSV_REQUIRED_HEADERS['ROLODEX.csv'].example}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
el.innerHTML = content;
|
||||
el.dataset.populated = 'true';
|
||||
}
|
||||
|
||||
el.classList.toggle('hidden');
|
||||
}
|
||||
// Import functionality
|
||||
let availableFiles = {};
|
||||
let importInProgress = false;
|
||||
@@ -328,6 +444,20 @@ function setupEventListeners() {
|
||||
document.getElementById('uploadMode').addEventListener('change', switchUploadMode);
|
||||
const helpBtn = document.getElementById('importHelpBtn');
|
||||
if (helpBtn) helpBtn.addEventListener('click', showImportHelp);
|
||||
const tfBtn = document.getElementById('downloadFilesTemplateBtn');
|
||||
const tlBtn = document.getElementById('downloadLedgerTemplateBtn');
|
||||
const tpBtn = document.getElementById('downloadPaymentsTemplateBtn');
|
||||
const trBtn = document.getElementById('downloadRolodexTemplateBtn');
|
||||
const ttBtn = document.getElementById('downloadTrnsactnTemplateBtn');
|
||||
const tdBtn = document.getElementById('downloadDepositsTemplateBtn');
|
||||
const tbBtn = document.getElementById('downloadTemplatesBundleBtn');
|
||||
if (tfBtn) tfBtn.addEventListener('click', () => downloadTemplateFor('FILES.csv'));
|
||||
if (tlBtn) tlBtn.addEventListener('click', () => downloadTemplateFor('LEDGER.csv'));
|
||||
if (tpBtn) tpBtn.addEventListener('click', () => downloadTemplateFor('PAYMENTS.csv'));
|
||||
if (trBtn) trBtn.addEventListener('click', () => downloadTemplateFor('ROLODEX.csv'));
|
||||
if (ttBtn) ttBtn.addEventListener('click', () => downloadTemplateFor('TRNSACTN.csv'));
|
||||
if (tdBtn) tdBtn.addEventListener('click', () => downloadTemplateFor('DEPOSITS.csv'));
|
||||
if (tbBtn) tbBtn.addEventListener('click', openTemplateBundleDialog);
|
||||
|
||||
// Validation buttons
|
||||
document.getElementById('validateBtn').addEventListener('click', validateFile);
|
||||
@@ -818,7 +948,7 @@ function showImportHelp() {
|
||||
</div>
|
||||
<div>
|
||||
Files will be imported in dependency order automatically:
|
||||
<code class="block mt-1">STATES.csv → GRUPLKUP.csv → EMPLOYEE.csv → FILETYPE.csv → FILESTAT.csv → TRNSTYPE.csv → TRNSLKUP.csv → FOOTERS.csv → SETUP.csv → PRINTERS.csv → ROLODEX.csv → PHONE.csv → FILES.csv → LEDGER.csv → TRNSACTN.csv → QDROS.csv → PENSIONS.csv → PLANINFO.csv → PAYMENTS.csv → DEPOSITS.csv → FILENOTS.csv → FORM_INX.csv → FORM_LST.csv → FVARLKUP.csv → RVARLKUP.csv</code>
|
||||
<code class="block mt-1">STATES.csv → GRUPLKUP.csv → EMPLOYEE.csv → FILETYPE.csv → FOOTERS.csv → FILESTAT.csv → TRNSTYPE.csv → TRNSLKUP.csv → SETUP.csv → PRINTERS.csv → ROLODEX.csv → PHONE.csv → FILES.csv → LEDGER.csv → TRNSACTN.csv → QDROS.csv → PENSIONS.csv → PLANINFO.csv → PAYMENTS.csv → DEPOSITS.csv → FILENOTS.csv → FORM_INX.csv → FORM_LST.csv → FVARLKUP.csv → RVARLKUP.csv</code>
|
||||
</div>
|
||||
<div>
|
||||
Unrecognized columns are saved as flexible JSON automatically. Unknown CSVs fall back to flexible-only storage.
|
||||
@@ -837,6 +967,104 @@ function showImportHelp() {
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadTemplateFor(type) {
|
||||
try {
|
||||
const resp = await window.http.wrappedFetch(`/api/import/template/${encodeURIComponent(type)}`);
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json().catch(() => ({}));
|
||||
throw new Error(err.detail || `Failed to download template for ${type}`);
|
||||
}
|
||||
const blob = await resp.blob();
|
||||
let filename = `${(type || '').replace('.csv','')}_template.csv`;
|
||||
const cd = resp.headers.get('content-disposition') || '';
|
||||
const m = cd.match(/filename="?([^";]+)"?/i);
|
||||
if (m && m[1]) filename = m[1];
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
} catch (error) {
|
||||
showAlert('Template download failed: ' + (error?.message || 'Unknown error'), 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
function openTemplateBundleDialog() {
|
||||
const content = `
|
||||
<div class="text-sm">
|
||||
<div class="mb-2">Select CSV templates to include:</div>
|
||||
<div class="space-y-2">
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="tpl" value="FILES.csv" checked> <span>FILES.csv</span></label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="tpl" value="LEDGER.csv" checked> <span>LEDGER.csv</span></label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="tpl" value="PAYMENTS.csv" checked> <span>PAYMENTS.csv</span></label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="tpl" value="TRNSACTN.csv" checked> <span>TRNSACTN.csv</span></label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="tpl" value="DEPOSITS.csv" checked> <span>DEPOSITS.csv</span></label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="tpl" value="ROLODEX.csv" checked> <span>ROLODEX.csv</span></label>
|
||||
</div>
|
||||
<div class="mt-3 text-xs text-neutral-500">A ZIP will be generated containing minimal templates with required headers and a sample row.</div>
|
||||
</div>
|
||||
`;
|
||||
if (window.alerts && window.alerts.show) {
|
||||
window.alerts.show(content, 'info', {
|
||||
html: true,
|
||||
duration: 0,
|
||||
title: 'Download Templates',
|
||||
actions: [
|
||||
{
|
||||
label: 'Download ZIP',
|
||||
classes: 'px-3 py-1 rounded text-xs bg-primary-600 text-white hover:bg-primary-700',
|
||||
onClick: async ({ wrapper }) => {
|
||||
const inputs = wrapper.querySelectorAll('input[name="tpl"]:checked');
|
||||
const files = Array.from(inputs).map(i => i.value);
|
||||
if (!files.length) {
|
||||
showAlert('Please select at least one template', 'warning');
|
||||
return;
|
||||
}
|
||||
await downloadTemplatesBundle(files);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel',
|
||||
classes: 'px-3 py-1 rounded text-xs bg-neutral-200 hover:bg-neutral-300 text-neutral-800 dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:text-neutral-200',
|
||||
autoClose: true
|
||||
}
|
||||
]
|
||||
});
|
||||
} else {
|
||||
showAlert('Template selection requires alerts UI. Please download single templates.', 'info');
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadTemplatesBundle(files) {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
for (const f of files) params.append('files', f);
|
||||
const resp = await window.http.wrappedFetch(`/api/import/templates/bundle?${params.toString()}`);
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json().catch(() => ({}));
|
||||
throw new Error(err.detail || 'Failed to download templates bundle');
|
||||
}
|
||||
const blob = await resp.blob();
|
||||
let filename = 'csv_templates.zip';
|
||||
const cd = resp.headers.get('content-disposition') || '';
|
||||
const m = cd.match(/filename="?([^";]+)"?/i);
|
||||
if (m && m[1]) filename = m[1];
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
} catch (error) {
|
||||
showAlert('Bundle download failed: ' + (error?.message || 'Unknown error'), 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function validateAllFiles() {
|
||||
const fileInput = document.getElementById('batchFiles');
|
||||
|
||||
@@ -936,7 +1164,9 @@ function displayBatchValidationResults(results, allValid) {
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-${resultIcon} text-${resultClass}-600 dark:text-${resultClass}-400"></i>
|
||||
<strong class="text-sm">${result.file_type}</strong>
|
||||
<strong class="text-sm flex items-center gap-2">${result.file_type}
|
||||
<i class="fa-solid fa-circle-info text-neutral-500" title="${getRequiredHeadersTooltip(result.file_type)}"></i>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="text-sm text-${resultClass}-600 dark:text-${resultClass}-400 capitalize">${status}</div>
|
||||
</div>
|
||||
@@ -948,6 +1178,11 @@ function displayBatchValidationResults(results, allValid) {
|
||||
html += `<p class="text-xs text-neutral-600 dark:text-neutral-400 mt-1">${mappedCount} mapped, ${unmappedCount} unmapped</p>`;
|
||||
}
|
||||
|
||||
if (result.header_validation && result.header_validation.ok === false) {
|
||||
const missing = (result.header_validation.missing_fields || []).join(', ');
|
||||
html += `<p class="text-xs text-danger-600 dark:text-danger-400 mt-1">Missing required headers: ${missing || 'unknown'}</p>`;
|
||||
}
|
||||
|
||||
if (result.total_errors > 0) {
|
||||
html += `<p class="text-xs text-neutral-600 dark:text-neutral-400 mt-1">${result.total_errors} data validation errors found</p>`;
|
||||
}
|
||||
@@ -1000,8 +1235,8 @@ function updateSelectedFiles() {
|
||||
if (files.length > 0) {
|
||||
// Define import order
|
||||
const importOrder = [
|
||||
"STATES.csv", "GRUPLKUP.csv", "EMPLOYEE.csv", "FILETYPE.csv", "FILESTAT.csv",
|
||||
"TRNSTYPE.csv", "TRNSLKUP.csv", "FOOTERS.csv", "SETUP.csv", "PRINTERS.csv",
|
||||
"STATES.csv", "GRUPLKUP.csv", "EMPLOYEE.csv", "FILETYPE.csv", "FOOTERS.csv", "FILESTAT.csv",
|
||||
"TRNSTYPE.csv", "TRNSLKUP.csv", "SETUP.csv", "PRINTERS.csv",
|
||||
"ROLODEX.csv", "PHONE.csv", "FILES.csv", "LEDGER.csv", "TRNSACTN.csv",
|
||||
"QDROS.csv", "PENSIONS.csv", "PLANINFO.csv", "PAYMENTS.csv", "DEPOSITS.csv",
|
||||
"FILENOTS.csv", "FORM_INX.csv", "FORM_LST.csv", "FVARLKUP.csv", "RVARLKUP.csv"
|
||||
@@ -1238,7 +1473,9 @@ function displayBatchResults(result) {
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-${statusIcon} text-${statusClass}-600 dark:text-${statusClass}-400"></i>
|
||||
<strong class="text-sm">${fileResult.file_type}</strong>
|
||||
<strong class="text-sm flex items-center gap-2">${fileResult.file_type}
|
||||
<i class="fa-solid fa-circle-info text-neutral-500" title="${getRequiredHeadersTooltip(fileResult.file_type)}"></i>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="text-right text-sm">
|
||||
${fileResult.imported_count ? `<span class="text-success-600 dark:text-success-400">${fileResult.imported_count} imported</span>` : ''}
|
||||
@@ -1252,6 +1489,11 @@ function displayBatchResults(result) {
|
||||
<span class=\"ml-2\">${(fileResult.auto_mapping.unmapped_headers || []).length} unmapped (stored as flexible)</span>
|
||||
</div>
|
||||
` : ''}
|
||||
${(fileResult.header_validation && fileResult.header_validation.ok === false) ? `
|
||||
<div class=\"mt-2 text-xs text-danger-600 dark:text-danger-400\">
|
||||
Missing required headers: ${(fileResult.header_validation.missing_fields || []).join(', ') || 'unknown'}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
@@ -1369,15 +1611,20 @@ async function viewAuditDetails(auditId) {
|
||||
const resp = await window.http.wrappedFetch(`/api/import/recent-batches/${auditId}`);
|
||||
if (!resp.ok) return;
|
||||
const data = await resp.json();
|
||||
const files = (data.files || []).map(f => `
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm font-mono">${f.file_type}</td>
|
||||
<td class="px-3 py-2 text-sm"><span class="inline-block px-2 py-0.5 rounded ${f.status === 'success' ? 'bg-green-100 text-green-700' : (f.status === 'completed_with_errors' ? 'bg-yellow-100 text-yellow-700' : 'bg-red-100 text-red-700')}">${f.status}</span></td>
|
||||
<td class="px-3 py-2 text-sm">${f.imported_count}</td>
|
||||
<td class="px-3 py-2 text-sm">${f.errors}</td>
|
||||
<td class="px-3 py-2 text-sm">${f.message || ''}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
const files = (data.files || []).map(f => {
|
||||
const hv = (f.details && f.details.header_validation) ? f.details.header_validation : null;
|
||||
const hvCell = hv && hv.ok === false ? `Missing: ${(hv.missing_fields || []).join(', ')}` : '—';
|
||||
return `
|
||||
<tr>
|
||||
<td class="px-3 py-2 text-sm font-mono">${f.file_type}</td>
|
||||
<td class="px-3 py-2 text-sm"><span class="inline-block px-2 py-0.5 rounded ${f.status === 'success' ? 'bg-green-100 text-green-700' : (f.status === 'completed_with_errors' ? 'bg-yellow-100 text-yellow-700' : 'bg-red-100 text-red-700')}">${f.status}</span></td>
|
||||
<td class="px-3 py-2 text-sm">${f.imported_count}</td>
|
||||
<td class="px-3 py-2 text-sm">${f.errors}</td>
|
||||
<td class="px-3 py-2 text-sm">${hvCell}</td>
|
||||
<td class="px-3 py-2 text-sm">${f.message || ''}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
const hasFailed = Number(data.audit.failed_files || 0) > 0;
|
||||
const content = `
|
||||
<div class="space-y-2 text-sm">
|
||||
@@ -1396,6 +1643,7 @@ async function viewAuditDetails(auditId) {
|
||||
<th class="px-3 py-2 text-left">Status</th>
|
||||
<th class="px-3 py-2 text-left">Imported</th>
|
||||
<th class="px-3 py-2 text-left">Errors</th>
|
||||
<th class="px-3 py-2 text-left">Header Issues</th>
|
||||
<th class="px-3 py-2 text-left">Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
Reference in New Issue
Block a user