438 lines
27 KiB
HTML
438 lines
27 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}{{ session.session_label or 'Session' }} — {{ project.name }}{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="max-w-5xl mx-auto">
|
||
<!-- Breadcrumb -->
|
||
<nav class="flex items-center space-x-2 text-sm text-gray-500 dark:text-gray-400 mb-4">
|
||
<a href="/projects" class="hover:text-seismo-orange">Projects</a>
|
||
<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="M9 5l7 7-7 7"></path>
|
||
</svg>
|
||
<a href="/projects/{{ project_id }}" class="hover:text-seismo-orange">{{ project.name }}</a>
|
||
<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="M9 5l7 7-7 7"></path>
|
||
</svg>
|
||
<span class="text-gray-900 dark:text-white truncate max-w-xs">{{ session.session_label or ('Session ' + session.id[:8] + '…') }}</span>
|
||
</nav>
|
||
|
||
<!-- Header -->
|
||
<div class="flex items-start justify-between gap-4 mb-6">
|
||
<div>
|
||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-3">
|
||
<svg class="w-7 h-7 text-seismo-orange" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
|
||
</svg>
|
||
<span id="header-label">{{ session.session_label or ('Session ' + session.id[:8] + '…') }}</span>
|
||
</h1>
|
||
{% if location %}
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ location.name }}{% if unit %} · {{ unit.id }}{% endif %}</p>
|
||
{% endif %}
|
||
</div>
|
||
<div class="flex items-center gap-2 shrink-0">
|
||
{% if session.status == 'completed' %}
|
||
<span class="px-3 py-1 text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 rounded-full">Completed</span>
|
||
{% elif session.status == 'recording' %}
|
||
<span class="px-3 py-1 text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300 rounded-full flex items-center gap-1">
|
||
<span class="w-2 h-2 bg-red-500 rounded-full animate-pulse"></span> Recording
|
||
</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
|
||
<!-- LEFT COLUMN: Info + Edit -->
|
||
<div class="lg:col-span-1 space-y-4">
|
||
|
||
<!-- Session Info Card -->
|
||
<div class="bg-white dark:bg-slate-800 rounded-xl border border-gray-200 dark:border-gray-700 p-5">
|
||
<h2 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Session Info</h2>
|
||
<dl class="space-y-2 text-sm">
|
||
<div class="flex justify-between">
|
||
<dt class="text-gray-500 dark:text-gray-400">Label</dt>
|
||
<dd class="font-medium text-gray-900 dark:text-white text-right max-w-[180px] truncate"
|
||
id="info-label">{{ session.session_label or '—' }}</dd>
|
||
</div>
|
||
<div class="flex justify-between">
|
||
<dt class="text-gray-500 dark:text-gray-400">Location</dt>
|
||
<dd class="font-medium text-gray-900 dark:text-white">{{ location.name if location else '—' }}</dd>
|
||
</div>
|
||
<div class="flex justify-between">
|
||
<dt class="text-gray-500 dark:text-gray-400">Period</dt>
|
||
<dd class="font-medium text-gray-900 dark:text-white" id="info-period">
|
||
{% set PLABELS = {'weekday_day':'Weekday Day','weekday_night':'Weekday Night','weekend_day':'Weekend Day','weekend_night':'Weekend Night'} %}
|
||
{{ PLABELS.get(session.period_type, '—') }}
|
||
</dd>
|
||
</div>
|
||
{% if effective_range %}
|
||
<div class="flex justify-between">
|
||
<dt class="text-gray-500 dark:text-gray-400">Effective</dt>
|
||
<dd class="font-medium text-indigo-600 dark:text-indigo-400 text-right text-xs" id="info-effective">{{ effective_range }}</dd>
|
||
</div>
|
||
{% else %}
|
||
<div class="flex justify-between hidden" id="info-effective-row">
|
||
<dt class="text-gray-500 dark:text-gray-400">Effective</dt>
|
||
<dd class="font-medium text-indigo-600 dark:text-indigo-400 text-right text-xs" id="info-effective"></dd>
|
||
</div>
|
||
{% endif %}
|
||
<div class="flex justify-between">
|
||
<dt class="text-gray-500 dark:text-gray-400">Report Date</dt>
|
||
<dd class="font-medium text-gray-900 dark:text-white" id="info-report-date">
|
||
{{ report_date or '— (auto)' }}
|
||
</dd>
|
||
</div>
|
||
{% if session.started_at %}
|
||
<div class="flex justify-between">
|
||
<dt class="text-gray-500 dark:text-gray-400">Started</dt>
|
||
<dd class="font-medium text-gray-900 dark:text-white text-right">{{ session.started_at|local_datetime }}</dd>
|
||
</div>
|
||
{% endif %}
|
||
{% if session.stopped_at %}
|
||
<div class="flex justify-between">
|
||
<dt class="text-gray-500 dark:text-gray-400">Ended</dt>
|
||
<dd class="font-medium text-gray-900 dark:text-white text-right">{{ session.stopped_at|local_datetime }}</dd>
|
||
</div>
|
||
{% endif %}
|
||
{% if session.duration_seconds %}
|
||
<div class="flex justify-between">
|
||
<dt class="text-gray-500 dark:text-gray-400">Duration</dt>
|
||
<dd class="font-medium text-gray-900 dark:text-white">{{ session.duration_seconds // 3600 }}h {{ (session.duration_seconds % 3600) // 60 }}m</dd>
|
||
</div>
|
||
{% endif %}
|
||
{% if session.device_model %}
|
||
<div class="flex justify-between">
|
||
<dt class="text-gray-500 dark:text-gray-400">Device Model</dt>
|
||
<dd class="font-medium text-gray-900 dark:text-white">{{ session.device_model }}</dd>
|
||
</div>
|
||
{% endif %}
|
||
{% if session_meta.get('store_name') %}
|
||
<div class="flex justify-between">
|
||
<dt class="text-gray-500 dark:text-gray-400">Store Name</dt>
|
||
<dd class="font-medium text-gray-900 dark:text-white">{{ session_meta.store_name }}</dd>
|
||
</div>
|
||
{% endif %}
|
||
{% if session_meta.get('serial_number') %}
|
||
<div class="flex justify-between">
|
||
<dt class="text-gray-500 dark:text-gray-400">Serial #</dt>
|
||
<dd class="font-medium text-gray-900 dark:text-white">{{ session_meta.serial_number }}</dd>
|
||
</div>
|
||
{% endif %}
|
||
</dl>
|
||
</div>
|
||
|
||
<!-- Edit Card -->
|
||
<div class="bg-white dark:bg-slate-800 rounded-xl border border-gray-200 dark:border-gray-700 p-5">
|
||
<h2 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-4">Edit Session</h2>
|
||
<form id="edit-form" onsubmit="saveSession(event)">
|
||
|
||
<!-- Label -->
|
||
<div class="mb-4">
|
||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">Label</label>
|
||
<input type="text" id="edit-label" name="session_label"
|
||
value="{{ session.session_label or '' }}"
|
||
placeholder="e.g. NRL-1 — Mon 3/24 — Night"
|
||
class="w-full text-sm bg-gray-50 dark:bg-slate-700 border border-gray-200 dark:border-gray-600 rounded-lg px-3 py-2 text-gray-900 dark:text-white focus:outline-none focus:border-seismo-orange">
|
||
</div>
|
||
|
||
<!-- Section: Required Recording Window -->
|
||
<div class="mb-4 p-3 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg border border-indigo-100 dark:border-indigo-800">
|
||
<p class="text-xs font-semibold text-indigo-700 dark:text-indigo-300 mb-0.5">Required Recording Window</p>
|
||
<p class="text-xs text-indigo-500 dark:text-indigo-400 mb-3">The hours that count for reports. Only data within this window is included.</p>
|
||
|
||
<div class="space-y-2">
|
||
<div>
|
||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">Period Type</label>
|
||
<select id="edit-period-type" name="period_type"
|
||
onchange="fillPeriodDefaults()"
|
||
class="w-full text-sm bg-white dark:bg-slate-700 border border-gray-200 dark:border-gray-600 rounded-lg px-3 py-2 text-gray-900 dark:text-white focus:outline-none focus:border-seismo-orange">
|
||
<option value="">— Not Set —</option>
|
||
<option value="weekday_day" {% if session.period_type == 'weekday_day' %}selected{% endif %}>Weekday Day</option>
|
||
<option value="weekday_night" {% if session.period_type == 'weekday_night' %}selected{% endif %}>Weekday Night</option>
|
||
<option value="weekend_day" {% if session.period_type == 'weekend_day' %}selected{% endif %}>Weekend Day</option>
|
||
<option value="weekend_night" {% if session.period_type == 'weekend_night' %}selected{% endif %}>Weekend Night</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-2">
|
||
<div>
|
||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">From (hour)</label>
|
||
<div class="relative">
|
||
<input type="number" min="0" max="23" id="edit-start-hour" name="period_start_hour"
|
||
value="{{ session.period_start_hour if session.period_start_hour is not none else '' }}"
|
||
placeholder="e.g. 19"
|
||
oninput="updateWindowPreview()"
|
||
class="w-full text-sm bg-white dark:bg-slate-700 border border-gray-200 dark:border-gray-600 rounded-lg px-3 py-2 text-gray-900 dark:text-white focus:outline-none focus:border-seismo-orange">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">To (hour)</label>
|
||
<input type="number" min="0" max="23" id="edit-end-hour" name="period_end_hour"
|
||
value="{{ session.period_end_hour if session.period_end_hour is not none else '' }}"
|
||
placeholder="e.g. 7"
|
||
oninput="updateWindowPreview()"
|
||
class="w-full text-sm bg-white dark:bg-slate-700 border border-gray-200 dark:border-gray-600 rounded-lg px-3 py-2 text-gray-900 dark:text-white focus:outline-none focus:border-seismo-orange">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Live preview -->
|
||
<div id="window-preview" class="text-xs font-medium text-indigo-600 dark:text-indigo-300 min-h-[1rem]">
|
||
{% if session.period_start_hour is not none and session.period_end_hour is not none %}
|
||
{% set sh = session.period_start_hour %}
|
||
{% set eh = session.period_end_hour %}
|
||
Window: {{ (sh % 12) or 12 }}:00 {{ 'AM' if sh < 12 else 'PM' }} → {{ (eh % 12) or 12 }}:00 {{ 'AM' if eh < 12 else 'PM' }}{% if eh <= sh %} (crosses midnight){% endif %}
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="mt-2">
|
||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">
|
||
Target Date <span class="text-gray-400">(optional — day sessions only)</span>
|
||
</label>
|
||
<input type="date" id="edit-report-date" name="report_date"
|
||
value="{{ report_date or '' }}"
|
||
class="w-full text-sm bg-white dark:bg-slate-700 border border-gray-200 dark:border-gray-600 rounded-lg px-3 py-2 text-gray-900 dark:text-white focus:outline-none focus:border-seismo-orange">
|
||
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">Leave blank to auto-select the last day with data in the window.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section: Device On/Off Times -->
|
||
<div class="mb-4 p-3 bg-gray-50 dark:bg-slate-700/40 rounded-lg border border-gray-200 dark:border-gray-600">
|
||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 mb-0.5">Device On/Off Times</p>
|
||
<p class="text-xs text-gray-400 dark:text-gray-500 mb-3">When the meter was actually running. Usually set automatically from the data file.</p>
|
||
|
||
<div class="space-y-2">
|
||
<div>
|
||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">Powered on</label>
|
||
<input type="datetime-local" id="edit-started-at" name="started_at"
|
||
value="{{ session.started_at|local_datetime_input if session.started_at else '' }}"
|
||
class="w-full text-sm bg-white dark:bg-slate-700 border border-gray-200 dark:border-gray-600 rounded-lg px-3 py-2 text-gray-900 dark:text-white focus:outline-none focus:border-seismo-orange">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">Powered off</label>
|
||
<input type="datetime-local" id="edit-stopped-at" name="stopped_at"
|
||
value="{{ session.stopped_at|local_datetime_input if session.stopped_at else '' }}"
|
||
class="w-full text-sm bg-white dark:bg-slate-700 border border-gray-200 dark:border-gray-600 rounded-lg px-3 py-2 text-gray-900 dark:text-white focus:outline-none focus:border-seismo-orange">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex gap-2">
|
||
<button type="submit"
|
||
class="flex-1 text-sm py-2 bg-seismo-orange text-white rounded-lg hover:bg-orange-600 transition-colors font-medium">
|
||
Save Changes
|
||
</button>
|
||
</div>
|
||
<div id="save-status" class="hidden text-xs text-center pt-2"></div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- RIGHT COLUMN: Files + Report Actions -->
|
||
<div class="lg:col-span-2 space-y-5">
|
||
|
||
<!-- Files List -->
|
||
<div class="bg-white dark:bg-slate-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||
<div class="px-5 py-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||
<h2 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">
|
||
Data Files
|
||
<span class="ml-2 text-xs font-normal text-gray-400">({{ files|length }})</span>
|
||
</h2>
|
||
</div>
|
||
{% if files %}
|
||
<div class="divide-y divide-gray-100 dark:divide-gray-700">
|
||
{% for f in files %}
|
||
{% set fname = f.file_path.split('/')[-1] %}
|
||
{% set is_rnd = fname.lower().endswith('.rnd') %}
|
||
{% set is_leq = '_leq_' in fname.lower() or fname.lower().startswith('au2_') %}
|
||
<div class="flex items-center gap-3 px-5 py-3 hover:bg-gray-50 dark:hover:bg-slate-700/50 transition-colors">
|
||
<!-- Icon -->
|
||
<div class="shrink-0 w-8 h-8 rounded-lg flex items-center justify-center
|
||
{% if is_rnd %}bg-green-100 dark:bg-green-900/30{% else %}bg-gray-100 dark:bg-gray-700{% endif %}">
|
||
{% if is_rnd %}
|
||
<svg class="w-4 h-4 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
|
||
</svg>
|
||
{% else %}
|
||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
||
</svg>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Name + meta -->
|
||
<div class="min-w-0 flex-1">
|
||
<div class="text-sm font-medium text-gray-900 dark:text-white truncate">{{ fname }}</div>
|
||
<div class="text-xs text-gray-400 flex items-center gap-2 mt-0.5">
|
||
<span>{{ f.file_type | upper }}</span>
|
||
{% if f.file_size_bytes %}
|
||
<span>{{ (f.file_size_bytes / 1024) | round(1) }} KB</span>
|
||
{% endif %}
|
||
{% if is_leq %}<span class="text-green-600 dark:text-green-400 font-medium">Leq</span>{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Actions -->
|
||
<div class="flex items-center gap-2 shrink-0">
|
||
{% if is_rnd %}
|
||
<a href="/api/projects/{{ project_id }}/files/{{ f.id }}/view-rnd"
|
||
class="px-2 py-1 text-xs bg-green-600 text-white rounded hover:bg-green-700 transition-colors">
|
||
View
|
||
</a>
|
||
{% if is_leq %}
|
||
<button onclick="openSingleFileReport('{{ f.id }}', '{{ fname }}')"
|
||
class="px-2 py-1 text-xs bg-emerald-600 text-white rounded hover:bg-emerald-700 transition-colors">
|
||
Report
|
||
</button>
|
||
{% endif %}
|
||
{% endif %}
|
||
<a href="/api/projects/{{ project_id }}/files/{{ f.id }}/download"
|
||
class="px-2 py-1 text-xs border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-400 rounded hover:bg-gray-100 dark:hover:bg-slate-600 transition-colors">
|
||
Download
|
||
</a>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="px-5 py-10 text-center text-gray-400">
|
||
<p>No files found for this session.</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Report Actions -->
|
||
<div class="bg-white dark:bg-slate-800 rounded-xl border border-gray-200 dark:border-gray-700 p-5">
|
||
<h2 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide mb-3">Report Actions</h2>
|
||
|
||
{% if session.status == 'completed' %}
|
||
{% set has_rnd = files | selectattr('file_type', 'equalto', 'rnd') | list | length > 0 %}
|
||
{% if has_rnd %}
|
||
<div class="p-3 bg-gray-50 dark:bg-slate-700/50 rounded-lg space-y-2">
|
||
<p class="text-xs text-gray-600 dark:text-gray-400">
|
||
Use the <strong>Combined Report Wizard</strong> to generate an Excel report for this session, or click <strong>View</strong> on a Leq file above to access per-file reporting.
|
||
{% if session.period_start_hour is not none %}
|
||
<br><span class="text-indigo-600 dark:text-indigo-400">Period window {{ session.period_start_hour }}:00–{{ session.period_end_hour }}:00 will be applied.</span>
|
||
{% endif %}
|
||
</p>
|
||
<a href="/projects/{{ project_id }}?tab=data"
|
||
class="inline-flex items-center gap-2 px-4 py-2 bg-emerald-600 text-white text-sm rounded-lg hover:bg-emerald-700 transition-colors font-medium">
|
||
<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="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>
|
||
Go to Combined Report Wizard
|
||
</a>
|
||
</div>
|
||
{% else %}
|
||
<p class="text-sm text-gray-400 dark:text-gray-500">No .rnd files found — upload data to generate a report.</p>
|
||
{% endif %}
|
||
{% else %}
|
||
<p class="text-sm text-gray-400 dark:text-gray-500">Reports are available after the session is completed.</p>
|
||
{% endif %}
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const PROJECT_ID = '{{ project_id }}';
|
||
const SESSION_ID = '{{ session.id }}';
|
||
|
||
const PERIOD_DEFAULT_HOURS = {
|
||
weekday_day: {start: 7, end: 19},
|
||
weekday_night: {start: 19, end: 7},
|
||
weekend_day: {start: 7, end: 19},
|
||
weekend_night: {start: 19, end: 7},
|
||
};
|
||
|
||
function fillPeriodDefaults() {
|
||
const pt = document.getElementById('edit-period-type').value;
|
||
const defaults = PERIOD_DEFAULT_HOURS[pt];
|
||
if (defaults) {
|
||
document.getElementById('edit-start-hour').value = defaults.start;
|
||
document.getElementById('edit-end-hour').value = defaults.end;
|
||
}
|
||
updateWindowPreview();
|
||
}
|
||
|
||
function updateWindowPreview() {
|
||
const sh = parseInt(document.getElementById('edit-start-hour').value, 10);
|
||
const eh = parseInt(document.getElementById('edit-end-hour').value, 10);
|
||
const el = document.getElementById('window-preview');
|
||
if (!el) return;
|
||
if (isNaN(sh) || isNaN(eh)) { el.textContent = ''; return; }
|
||
function fmt(h) { return `${h % 12 || 12}:00 ${h < 12 ? 'AM' : 'PM'}`; }
|
||
const crosses = eh <= sh;
|
||
el.textContent = `Window: ${fmt(sh)} → ${fmt(eh)}${crosses ? ' (crosses midnight)' : ''}`;
|
||
}
|
||
|
||
// Run once on load to populate preview if values already set
|
||
document.addEventListener('DOMContentLoaded', updateWindowPreview);
|
||
|
||
async function saveSession(e) {
|
||
e.preventDefault();
|
||
const status = document.getElementById('save-status');
|
||
status.className = 'text-xs text-center pt-1 text-gray-400';
|
||
status.textContent = 'Saving…';
|
||
status.classList.remove('hidden');
|
||
|
||
const form = document.getElementById('edit-form');
|
||
const payload = {};
|
||
|
||
const label = form.session_label.value.trim();
|
||
payload.session_label = label || null;
|
||
|
||
const pt = form.period_type.value;
|
||
payload.period_type = pt || null;
|
||
|
||
const sh = form.period_start_hour.value;
|
||
const eh = form.period_end_hour.value;
|
||
payload.period_start_hour = sh !== '' ? parseInt(sh, 10) : null;
|
||
payload.period_end_hour = eh !== '' ? parseInt(eh, 10) : null;
|
||
|
||
const rd = form.report_date.value;
|
||
payload.report_date = rd || null;
|
||
|
||
const sa = form.started_at.value;
|
||
if (sa) payload.started_at = sa;
|
||
|
||
const st = form.stopped_at.value;
|
||
if (st) payload.stopped_at = st;
|
||
else if (form.stopped_at.value === '') payload.stopped_at = null;
|
||
|
||
try {
|
||
const resp = await fetch(`/api/projects/${PROJECT_ID}/sessions/${SESSION_ID}`, {
|
||
method: 'PATCH',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify(payload),
|
||
});
|
||
if (!resp.ok) throw new Error(await resp.text());
|
||
const result = await resp.json();
|
||
|
||
// Update displayed label
|
||
const newLabel = result.session_label || ('Session ' + SESSION_ID.slice(0, 8) + '…');
|
||
document.getElementById('header-label').textContent = newLabel;
|
||
document.getElementById('info-label').textContent = result.session_label || '—';
|
||
document.getElementById('info-period').textContent = {
|
||
weekday_day: 'Weekday Day', weekday_night: 'Weekday Night',
|
||
weekend_day: 'Weekend Day', weekend_night: 'Weekend Night'
|
||
}[result.period_type] || '—';
|
||
document.getElementById('info-report-date').textContent = result.report_date || '— (auto)';
|
||
|
||
status.className = 'text-xs text-center pt-1 text-green-600 dark:text-green-400';
|
||
status.textContent = 'Saved!';
|
||
setTimeout(() => status.classList.add('hidden'), 2500);
|
||
} catch(err) {
|
||
status.className = 'text-xs text-center pt-1 text-red-500';
|
||
status.textContent = 'Error: ' + err.message;
|
||
}
|
||
}
|
||
|
||
function openSingleFileReport(fileId, filename) {
|
||
window.location.href = `/api/projects/${PROJECT_ID}/files/${fileId}/view-rnd`;
|
||
}
|
||
</script>
|
||
{% endblock %}
|