- Implemented a new API router for managing report templates, including endpoints for listing, creating, retrieving, updating, and deleting templates. - Added a new HTML partial for a unified SLM settings modal, allowing users to configure SLM settings with dynamic modem selection and FTP credentials. - Created a report preview page with an editable data table using jspreadsheet, enabling users to modify report details and download the report as an Excel file.
310 lines
12 KiB
HTML
310 lines
12 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Report Preview - {{ project.name if project else 'Sound Level Data' }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- jspreadsheet CSS -->
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jspreadsheet-ce@4/dist/jspreadsheet.min.css" />
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jsuites@5/dist/jsuites.min.css" />
|
|
|
|
<div class="min-h-screen bg-gray-100 dark:bg-slate-900">
|
|
<!-- Header -->
|
|
<div class="bg-white dark:bg-slate-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
|
|
Report Preview & Editor
|
|
</h1>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
|
{% if file %}{{ file.file_path.split('/')[-1] }}{% endif %}
|
|
{% if location %} @ {{ location.name }}{% endif %}
|
|
{% if start_time and end_time %} | Time: {{ start_time }} - {{ end_time }}{% endif %}
|
|
| {{ filtered_count }} of {{ original_count }} rows
|
|
</p>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<button onclick="downloadReport()"
|
|
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="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
|
|
</svg>
|
|
Download Excel
|
|
</button>
|
|
<a href="/api/projects/{{ project_id }}/files/{{ file_id }}/view-rnd"
|
|
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">
|
|
Back to Viewer
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Report Info Section -->
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
|
<div class="bg-white dark:bg-slate-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 mb-4">
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Report Title</label>
|
|
<input type="text" id="edit-report-title" value="{{ report_title }}"
|
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Project Name</label>
|
|
<input type="text" id="edit-project-name" value="{{ project_name }}"
|
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Client Name</label>
|
|
<input type="text" id="edit-client-name" value="{{ client_name }}"
|
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Location</label>
|
|
<input type="text" id="edit-location-name" value="{{ location_name }}"
|
|
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Spreadsheet Editor -->
|
|
<div class="bg-white dark:bg-slate-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Data Table</h2>
|
|
<div class="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
|
|
<span>Right-click for options</span>
|
|
<span class="text-gray-300 dark:text-gray-600">|</span>
|
|
<span>Double-click to edit</span>
|
|
</div>
|
|
</div>
|
|
<div id="spreadsheet" class="overflow-x-auto"></div>
|
|
</div>
|
|
|
|
<!-- Help Text -->
|
|
<div class="mt-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
|
<h3 class="text-sm font-medium text-blue-800 dark:text-blue-300 mb-2">Editing Tips</h3>
|
|
<ul class="text-sm text-blue-700 dark:text-blue-400 list-disc list-inside space-y-1">
|
|
<li>Double-click any cell to edit its value</li>
|
|
<li>Use the Comments column to add notes about specific measurements</li>
|
|
<li>Right-click a row to delete it from the report</li>
|
|
<li>Right-click to add new rows if needed</li>
|
|
<li>Press Enter to confirm edits, Escape to cancel</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- jspreadsheet JS -->
|
|
<script src="https://cdn.jsdelivr.net/npm/jsuites@5/dist/jsuites.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/jspreadsheet-ce@4/dist/index.min.js"></script>
|
|
|
|
<script>
|
|
// Initialize spreadsheet data from server
|
|
const initialData = {{ spreadsheet_data | tojson }};
|
|
|
|
// Create jspreadsheet instance
|
|
let spreadsheet = null;
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
spreadsheet = jspreadsheet(document.getElementById('spreadsheet'), {
|
|
data: initialData,
|
|
columns: [
|
|
{ title: 'Test #', width: 80, type: 'numeric' },
|
|
{ title: 'Date', width: 110, type: 'text' },
|
|
{ title: 'Time', width: 90, type: 'text' },
|
|
{ title: 'LAmax (dBA)', width: 100, type: 'numeric' },
|
|
{ title: 'LA01 (dBA)', width: 100, type: 'numeric' },
|
|
{ title: 'LA10 (dBA)', width: 100, type: 'numeric' },
|
|
{ title: 'Comments', width: 250, type: 'text' }
|
|
],
|
|
allowInsertRow: true,
|
|
allowDeleteRow: true,
|
|
allowInsertColumn: false,
|
|
allowDeleteColumn: false,
|
|
rowDrag: true,
|
|
columnSorting: true,
|
|
search: true,
|
|
pagination: 50,
|
|
paginationOptions: [25, 50, 100, 200],
|
|
defaultColWidth: 100,
|
|
minDimensions: [7, 1],
|
|
tableOverflow: true,
|
|
tableWidth: '100%',
|
|
contextMenu: function(instance, col, row, e) {
|
|
const items = [];
|
|
|
|
if (row !== null) {
|
|
items.push({
|
|
title: 'Insert row above',
|
|
onclick: function() {
|
|
instance.insertRow(1, row, true);
|
|
}
|
|
});
|
|
items.push({
|
|
title: 'Insert row below',
|
|
onclick: function() {
|
|
instance.insertRow(1, row + 1, false);
|
|
}
|
|
});
|
|
items.push({
|
|
title: 'Delete this row',
|
|
onclick: function() {
|
|
instance.deleteRow(row);
|
|
}
|
|
});
|
|
}
|
|
|
|
return items;
|
|
},
|
|
style: {
|
|
A: 'text-align: center;',
|
|
B: 'text-align: center;',
|
|
C: 'text-align: center;',
|
|
D: 'text-align: right;',
|
|
E: 'text-align: right;',
|
|
F: 'text-align: right;',
|
|
}
|
|
});
|
|
});
|
|
|
|
async function downloadReport() {
|
|
// Get current data from spreadsheet
|
|
const data = spreadsheet.getData();
|
|
|
|
// Get report settings
|
|
const reportTitle = document.getElementById('edit-report-title').value;
|
|
const projectName = document.getElementById('edit-project-name').value;
|
|
const clientName = document.getElementById('edit-client-name').value;
|
|
const locationName = document.getElementById('edit-location-name').value;
|
|
|
|
// Build time filter info
|
|
let timeFilter = '';
|
|
{% if start_time and end_time %}
|
|
timeFilter = 'Time Filter: {{ start_time }} - {{ end_time }}';
|
|
{% if start_date or end_date %}
|
|
timeFilter += ' | Date Range: {{ start_date or "start" }} to {{ end_date or "end" }}';
|
|
{% endif %}
|
|
timeFilter += ' | {{ filtered_count }} of {{ original_count }} rows';
|
|
{% endif %}
|
|
|
|
// Send to server to generate Excel
|
|
try {
|
|
const response = await fetch('/api/projects/{{ project_id }}/files/{{ file_id }}/generate-from-preview', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
data: data,
|
|
report_title: reportTitle,
|
|
project_name: projectName,
|
|
client_name: clientName,
|
|
location_name: locationName,
|
|
time_filter: timeFilter
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
// Download the file
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
|
|
// Get filename from Content-Disposition header if available
|
|
const contentDisposition = response.headers.get('Content-Disposition');
|
|
let filename = 'report.xlsx';
|
|
if (contentDisposition) {
|
|
const match = contentDisposition.match(/filename="(.+)"/);
|
|
if (match) filename = match[1];
|
|
}
|
|
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
a.remove();
|
|
} else {
|
|
const error = await response.json();
|
|
alert('Error generating report: ' + (error.detail || 'Unknown error'));
|
|
}
|
|
} catch (error) {
|
|
alert('Error generating report: ' + error.message);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
/* Custom styles for jspreadsheet to match dark mode */
|
|
.dark .jexcel {
|
|
background-color: #1e293b;
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
.dark .jexcel thead td {
|
|
background-color: #334155 !important;
|
|
color: #e2e8f0 !important;
|
|
border-color: #475569 !important;
|
|
}
|
|
|
|
.dark .jexcel tbody td {
|
|
background-color: #1e293b;
|
|
color: #e2e8f0;
|
|
border-color: #475569;
|
|
}
|
|
|
|
.dark .jexcel tbody td:hover {
|
|
background-color: #334155;
|
|
}
|
|
|
|
.dark .jexcel tbody tr:nth-child(even) td {
|
|
background-color: #0f172a;
|
|
}
|
|
|
|
.dark .jexcel_pagination {
|
|
background-color: #1e293b;
|
|
color: #e2e8f0;
|
|
border-color: #475569;
|
|
}
|
|
|
|
.dark .jexcel_pagination a {
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
.dark .jexcel_search {
|
|
background-color: #1e293b;
|
|
color: #e2e8f0;
|
|
border-color: #475569;
|
|
}
|
|
|
|
.dark .jexcel_search input {
|
|
background-color: #334155;
|
|
color: #e2e8f0;
|
|
border-color: #475569;
|
|
}
|
|
|
|
.dark .jexcel_content {
|
|
background-color: #1e293b;
|
|
}
|
|
|
|
.dark .jexcel_contextmenu {
|
|
background-color: #1e293b;
|
|
border-color: #475569;
|
|
}
|
|
|
|
.dark .jexcel_contextmenu a {
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
.dark .jexcel_contextmenu a:hover {
|
|
background-color: #334155;
|
|
}
|
|
|
|
/* Ensure proper sizing */
|
|
.jexcel_content {
|
|
max-height: 600px;
|
|
overflow: auto;
|
|
}
|
|
</style>
|
|
{% endblock %}
|