feat(reports): add "Night Report" button to sound project header

Sound projects only: a Night Report button next to "Generate Combined Report"
opens a small modal (pick night + optional baseline range) that opens the
rendered report (/reports/nightly/view) in a new tab. Defaults the night to
last night; baseline is optional.

Verified the header partial renders and the button is gated to sound_monitoring
(hidden on vibration-only projects); modal + JS wired.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 00:59:50 +00:00
parent 786a9821a3
commit 08d3d53702
@@ -74,6 +74,14 @@
</svg>
Generate Combined Report
</a>
<button onclick="openNightReportModal()"
title="Last night's noise vs baseline, per location (FTP report pipeline)"
class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-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="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
</svg>
Night Report
</button>
{% endif %}
<button onclick="openMergeModal()"
title="Merge this project into another (consolidates duplicates)"
@@ -87,6 +95,65 @@
</div>
</div>
<!-- Night Report Modal -->
<div id="night-report-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-2xl w-full max-w-md mx-4">
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Night Report</h3>
<button onclick="closeNightReportModal()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg>
</button>
</div>
<div class="px-6 py-5 space-y-4">
<p class="text-xs text-gray-500 dark:text-gray-400">Last night's noise (7&nbsp;PM7&nbsp;AM) vs a baseline range, per location. Opens in a new tab.</p>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Night (evening date)</label>
<input type="date" id="nr-night-date" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-slate-700 text-gray-900 dark:text-white text-sm">
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Baseline start <span class="text-gray-400 font-normal">(optional)</span></label>
<input type="date" id="nr-baseline-start" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-slate-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">Baseline end</label>
<input type="date" id="nr-baseline-end" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-slate-700 text-gray-900 dark:text-white text-sm">
</div>
</div>
</div>
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end gap-2">
<button onclick="closeNightReportModal()" class="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-sm">Cancel</button>
<button onclick="viewNightReport('{{ project.id }}')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors text-sm">View Report</button>
</div>
</div>
</div>
<script>
function openNightReportModal() {
var el = document.getElementById('nr-night-date');
if (el && !el.value) { // default to last night
var d = new Date(); d.setDate(d.getDate() - 1);
el.value = d.getFullYear() + '-'
+ String(d.getMonth() + 1).padStart(2, '0') + '-'
+ String(d.getDate()).padStart(2, '0');
}
document.getElementById('night-report-modal').classList.remove('hidden');
}
function closeNightReportModal() {
document.getElementById('night-report-modal').classList.add('hidden');
}
function viewNightReport(projectId) {
var night = document.getElementById('nr-night-date').value;
var bs = document.getElementById('nr-baseline-start').value;
var be = document.getElementById('nr-baseline-end').value;
if (!night) { alert('Pick a night (evening date).'); return; }
if ((bs && !be) || (be && !bs)) { alert('Provide both baseline dates, or leave both empty.'); return; }
var url = '/api/projects/' + projectId + '/reports/nightly/view?night_date=' + night;
if (bs && be) url += '&baseline_start=' + bs + '&baseline_end=' + be;
window.open(url, '_blank');
closeNightReportModal();
}
</script>
<!-- Merge Modal —
min-h on the body ensures the typeahead dropdown has room to render
below the input without forcing the operator to scroll inside the