feat: SLM project report generator added. WIP
This commit is contained in:
@@ -1,14 +1,30 @@
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">{{ project.name }}</h1>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium
|
||||
{% if project.status == 'active' %}bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200
|
||||
{% elif project.status == 'completed' %}bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200
|
||||
{% else %}bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200{% endif %}">
|
||||
{{ project.status|title }}
|
||||
</span>
|
||||
{% if project_type %}
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ project_type.name }}</span>
|
||||
{% endif %}
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">{{ project.name }}</h1>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium
|
||||
{% if project.status == 'active' %}bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200
|
||||
{% elif project.status == 'completed' %}bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200
|
||||
{% else %}bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200{% endif %}">
|
||||
{{ project.status|title }}
|
||||
</span>
|
||||
{% if project_type %}
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ project_type.name }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Project Actions -->
|
||||
<div class="flex items-center gap-3">
|
||||
{% if project_type and project_type.id == 'sound_monitoring' %}
|
||||
<a href="/api/projects/{{ project.id }}/generate-combined-report"
|
||||
class="px-4 py-2 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 transition-colors flex items-center gap-2 text-sm">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
Generate Combined Report
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -42,6 +42,24 @@
|
||||
{% else %}bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300{% endif %}">
|
||||
{{ session.status or 'unknown' }}
|
||||
</span>
|
||||
<!-- Download All Files in Session -->
|
||||
<button onclick="event.stopPropagation(); downloadSessionFiles('{{ session.id }}')"
|
||||
class="px-3 py-1 text-xs bg-seismo-orange text-white rounded-lg hover:bg-seismo-navy transition-colors flex items-center gap-1"
|
||||
title="Download all files in this session">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
|
||||
</svg>
|
||||
Download All
|
||||
</button>
|
||||
<!-- Delete Session -->
|
||||
<button onclick="event.stopPropagation(); confirmDeleteSession('{{ session.id }}', '{{ files|length }}')"
|
||||
class="px-3 py-1 text-xs bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors flex items-center gap-1"
|
||||
title="Delete session and all files">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,6 +125,17 @@
|
||||
{{ file.file_type or 'unknown' }}
|
||||
</span>
|
||||
|
||||
{# Leq vs Lp badge for RND files #}
|
||||
{% if file.file_path and '_Leq_' in file.file_path %}
|
||||
<span class="px-1.5 py-0.5 rounded font-medium bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300">
|
||||
Leq (15-min avg)
|
||||
</span>
|
||||
{% elif file.file_path and '_Lp' in file.file_path and file.file_path.endswith('.rnd') %}
|
||||
<span class="px-1.5 py-0.5 rounded font-medium bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300">
|
||||
Lp (instant)
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<!-- File Size -->
|
||||
<span class="mx-1">•</span>
|
||||
{% if file.file_size_bytes %}
|
||||
@@ -159,6 +188,18 @@
|
||||
View
|
||||
</a>
|
||||
{% endif %}
|
||||
{# Only show Report button for Leq files (15-min averaged data with LN percentiles) #}
|
||||
{% if '_Leq_' in file.file_path %}
|
||||
<a href="/api/projects/{{ project_id }}/files/{{ file.id }}/generate-report"
|
||||
onclick="event.stopPropagation();"
|
||||
class="px-3 py-1 text-xs bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center"
|
||||
title="Generate Excel Report">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
Report
|
||||
</a>
|
||||
{% endif %}
|
||||
<button onclick="event.stopPropagation(); downloadFile('{{ file.id }}')"
|
||||
class="px-3 py-1 text-xs bg-seismo-orange text-white rounded-lg hover:bg-seismo-navy transition-colors">
|
||||
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -166,6 +207,13 @@
|
||||
</svg>
|
||||
Download
|
||||
</button>
|
||||
<button onclick="event.stopPropagation(); confirmDeleteFile('{{ file.id }}', '{{ file.file_path.split('/')[-1] if file.file_path else 'Unknown' }}')"
|
||||
class="px-3 py-1 text-xs bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
|
||||
title="Delete this file">
|
||||
<svg class="w-4 h-4 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -212,4 +260,56 @@ function toggleSession(sessionId, headerElement) {
|
||||
function downloadFile(fileId) {
|
||||
window.location.href = `/api/projects/{{ project_id }}/files/${fileId}/download`;
|
||||
}
|
||||
|
||||
function downloadSessionFiles(sessionId) {
|
||||
window.location.href = `/api/projects/{{ project_id }}/sessions/${sessionId}/download-all`;
|
||||
}
|
||||
|
||||
function confirmDeleteFile(fileId, fileName) {
|
||||
if (confirm(`Are you sure you want to delete "${fileName}"?\n\nThis action cannot be undone.`)) {
|
||||
deleteFile(fileId);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteFile(fileId) {
|
||||
try {
|
||||
const response = await fetch(`/api/projects/{{ project_id }}/files/${fileId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Reload the files list
|
||||
window.location.reload();
|
||||
} else {
|
||||
const data = await response.json();
|
||||
alert(`Failed to delete file: ${data.detail || 'Unknown error'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`Error deleting file: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDeleteSession(sessionId, fileCount) {
|
||||
if (confirm(`Are you sure you want to delete this session and all ${fileCount} file(s)?\n\nThis action cannot be undone.`)) {
|
||||
deleteSession(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSession(sessionId) {
|
||||
try {
|
||||
const response = await fetch(`/api/projects/{{ project_id }}/sessions/${sessionId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Reload the files list
|
||||
window.location.reload();
|
||||
} else {
|
||||
const data = await response.json();
|
||||
alert(`Failed to delete session: ${data.detail || 'Unknown error'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(`Error deleting session: ${error.message}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -59,12 +59,23 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
{# Only show Report button for Leq files (15-min averaged data with LN percentiles) #}
|
||||
{% if file and '_Leq_' in file.file_path %}
|
||||
<!-- Generate Excel Report Button -->
|
||||
<button onclick="openReportModal()"
|
||||
class="px-4 py-2 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 transition-colors flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
Generate Excel Report
|
||||
</button>
|
||||
{% endif %}
|
||||
<a href="/api/projects/{{ project_id }}/files/{{ file_id }}/download"
|
||||
class="px-4 py-2 bg-seismo-orange text-white rounded-lg hover:bg-seismo-navy transition-colors flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
|
||||
</svg>
|
||||
Download File
|
||||
Download RND
|
||||
</a>
|
||||
<a href="/projects/{{ project_id }}"
|
||||
class="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">
|
||||
@@ -441,5 +452,115 @@ function escapeHtml(str) {
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Report Generation Modal Functions
|
||||
function openReportModal() {
|
||||
document.getElementById('report-modal').classList.remove('hidden');
|
||||
// Pre-fill location name if available
|
||||
const locationInput = document.getElementById('report-location');
|
||||
if (locationInput && !locationInput.value) {
|
||||
locationInput.value = '{{ location.name if location else "" }}';
|
||||
}
|
||||
}
|
||||
|
||||
function closeReportModal() {
|
||||
document.getElementById('report-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function generateReport() {
|
||||
const reportTitle = document.getElementById('report-title').value || 'Background Noise Study';
|
||||
const locationName = document.getElementById('report-location').value || '';
|
||||
|
||||
// Build the URL with query parameters
|
||||
const params = new URLSearchParams({
|
||||
report_title: reportTitle,
|
||||
location_name: locationName
|
||||
});
|
||||
|
||||
// Trigger download
|
||||
window.location.href = `/api/projects/{{ project_id }}/files/{{ file_id }}/generate-report?${params.toString()}`;
|
||||
|
||||
// Close modal
|
||||
closeReportModal();
|
||||
}
|
||||
|
||||
// Close modal on escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeReportModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Report Generation Modal -->
|
||||
<div id="report-modal" class="hidden fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
||||
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<!-- Background overlay -->
|
||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 dark:bg-gray-900 dark:bg-opacity-75 transition-opacity" onclick="closeReportModal()"></div>
|
||||
|
||||
<!-- Modal panel -->
|
||||
<div class="inline-block align-bottom bg-white dark:bg-slate-800 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
||||
<div class="bg-white dark:bg-slate-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-emerald-100 dark:bg-emerald-900/30 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<svg class="h-6 w-6 text-emerald-600 dark:text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left flex-1">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white" id="modal-title">
|
||||
Generate Excel Report
|
||||
</h3>
|
||||
<div class="mt-4 space-y-4">
|
||||
<!-- Report Title -->
|
||||
<div>
|
||||
<label for="report-title" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Report Title
|
||||
</label>
|
||||
<input type="text" id="report-title" value="Background Noise Study"
|
||||
class="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 sm:text-sm"
|
||||
placeholder="e.g., Background Noise Study - Commercial Street Bridge">
|
||||
</div>
|
||||
|
||||
<!-- Location Name -->
|
||||
<div>
|
||||
<label for="report-location" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Location Name
|
||||
</label>
|
||||
<input type="text" id="report-location" value=""
|
||||
class="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-emerald-500 focus:border-emerald-500 sm:text-sm"
|
||||
placeholder="e.g., NRL 1 - West Side">
|
||||
</div>
|
||||
|
||||
<!-- Info about what's included -->
|
||||
<div class="bg-gray-50 dark:bg-gray-900/50 rounded-lg p-3">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
<strong>Report will include:</strong>
|
||||
</p>
|
||||
<ul class="mt-1 text-xs text-gray-500 dark:text-gray-400 list-disc list-inside space-y-0.5">
|
||||
<li>Data table (Test #, Date, Time, LAmax, LA01, LA10, Comments)</li>
|
||||
<li>Line chart visualization</li>
|
||||
<li>Time period summary (Evening, Night, Morning, Daytime)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-gray-900/50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse gap-2">
|
||||
<button type="button" onclick="generateReport()"
|
||||
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-emerald-600 text-base font-medium text-white hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500 sm:w-auto sm:text-sm">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
|
||||
</svg>
|
||||
Generate & Download
|
||||
</button>
|
||||
<button type="button" onclick="closeReportModal()"
|
||||
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 shadow-sm px-4 py-2 bg-white dark:bg-gray-700 text-base font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500 sm:mt-0 sm:w-auto sm:text-sm">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user