Add logbook entries, data export page, and JSON import
This commit is contained in:
@@ -334,6 +334,20 @@
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<footer style="text-align:center;padding:1.25rem 1rem 1.5rem;margin-top:1rem;
|
||||
border-top:1px solid var(--border);font-size:0.8rem;">
|
||||
<a href="{{ url_for('export_page') }}"
|
||||
style="color:var(--text-muted);text-decoration:none;"
|
||||
onmouseover="this.style.textDecoration='underline'"
|
||||
onmouseout="this.style.textDecoration='none'">Export data</a>
|
||||
<span style="color:var(--text-muted);margin:0 0.5rem;">·</span>
|
||||
<a href="{{ url_for('import_page') }}"
|
||||
style="color:var(--text-muted);text-decoration:none;"
|
||||
onmouseover="this.style.textDecoration='underline'"
|
||||
onmouseout="this.style.textDecoration='none'">Import data</a>
|
||||
</footer>
|
||||
|
||||
<div id="confirm-modal" role="dialog" aria-modal="true">
|
||||
<div id="confirm-modal-box">
|
||||
<p id="confirm-modal-msg"></p>
|
||||
|
||||
@@ -216,6 +216,38 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Logbook -->
|
||||
<div class="card">
|
||||
<h2>Logbook</h2>
|
||||
{% if logbook_entries %}
|
||||
<div style="display:flex;flex-direction:column;gap:0.6rem;margin-bottom:1rem;">
|
||||
{% for entry in logbook_entries %}
|
||||
<div style="border-left:3px solid var(--border);padding:0.35rem 0.75rem;
|
||||
display:flex;justify-content:space-between;align-items:flex-start;gap:0.5rem;">
|
||||
<div>
|
||||
<div class="text-muted" style="font-size:0.78rem;margin-bottom:0.15rem;">{{ entry.recorded_at }}</div>
|
||||
<div style="white-space:pre-wrap;">{{ entry.body }}</div>
|
||||
</div>
|
||||
<form method="post"
|
||||
action="{{ url_for('battery_logbook_delete', battery_id=battery.id, entry_id=entry.id) }}"
|
||||
data-confirm="Delete this logbook entry?" data-confirm-ok="Delete">
|
||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted" style="margin-bottom:0.75rem;">No logbook entries yet.</p>
|
||||
{% endif %}
|
||||
<h3 style="font-size:1rem;margin:0.75rem 0 0.5rem;color:var(--text-h2);">Add Entry</h3>
|
||||
<form method="post" action="{{ url_for('battery_logbook_add', battery_id=battery.id) }}">
|
||||
<div class="form-group" style="margin-bottom:0.5rem;">
|
||||
<textarea name="body" placeholder="Write a note…" rows="2" required style="min-height:60px;"></textarea>
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Add Entry</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Edit Details -->
|
||||
<div class="card">
|
||||
<h2>Edit Details</h2>
|
||||
|
||||
@@ -221,6 +221,37 @@ function addInstallRow() {
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Logbook</h2>
|
||||
{% if logbook_entries %}
|
||||
<div style="display:flex;flex-direction:column;gap:0.6rem;margin-bottom:1rem;">
|
||||
{% for entry in logbook_entries %}
|
||||
<div style="border-left:3px solid var(--border);padding:0.35rem 0.75rem;
|
||||
display:flex;justify-content:space-between;align-items:flex-start;gap:0.5rem;">
|
||||
<div>
|
||||
<div class="text-muted" style="font-size:0.78rem;margin-bottom:0.15rem;">{{ entry.recorded_at }}</div>
|
||||
<div style="white-space:pre-wrap;">{{ entry.body }}</div>
|
||||
</div>
|
||||
<form method="post"
|
||||
action="{{ url_for('device_logbook_delete', device_id=device.id, entry_id=entry.id) }}"
|
||||
data-confirm="Delete this logbook entry?" data-confirm-ok="Delete">
|
||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted" style="margin-bottom:0.75rem;">No logbook entries yet.</p>
|
||||
{% endif %}
|
||||
<h3 style="font-size:1rem;margin:0.75rem 0 0.5rem;color:var(--text-h2);">Add Entry</h3>
|
||||
<form method="post" action="{{ url_for('device_logbook_add', device_id=device.id) }}">
|
||||
<div class="form-group" style="margin-bottom:0.5rem;">
|
||||
<textarea name="body" placeholder="Write a note…" rows="2" required style="min-height:60px;"></textarea>
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Add Entry</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Edit Device</h2>
|
||||
<form method="post" action="{{ url_for('device_edit', device_id=device.id) }}">
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Export — Battery Tracker{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 style="margin-bottom:1.25rem;">Export Data</h1>
|
||||
|
||||
<div class="card" style="margin-bottom:1rem;">
|
||||
<h2 style="margin-bottom:0.5rem;">Full Export</h2>
|
||||
<p style="color:var(--text-muted,#6b7280);margin-bottom:1rem;">
|
||||
Download your complete dataset — batteries, devices, charge logs, capacity tests, and percentage history.
|
||||
</p>
|
||||
<div style="display:flex;gap:0.75rem;flex-wrap:wrap;">
|
||||
<a href="{{ url_for('export_csv_zip') }}" class="btn btn-primary">Download All CSVs (.zip)</a>
|
||||
<a href="{{ url_for('export_json') }}" class="btn btn-secondary">Download JSON</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2 style="margin-bottom:0.5rem;">Individual CSV Files</h2>
|
||||
<p style="color:var(--text-muted,#6b7280);margin-bottom:1rem;">Download a single table at a time.</p>
|
||||
<table style="border-collapse:collapse;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:0.4rem 0.75rem 0.4rem 0;">
|
||||
<a href="{{ url_for('export_batteries_csv') }}" class="btn btn-sm btn-secondary">batteries.csv</a>
|
||||
</td>
|
||||
<td style="padding:0.4rem 0;color:var(--text-muted,#6b7280);font-size:0.9rem;">
|
||||
All batteries with device name
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0.4rem 0.75rem 0.4rem 0;">
|
||||
<a href="{{ url_for('export_devices_csv') }}" class="btn btn-sm btn-secondary">devices.csv</a>
|
||||
</td>
|
||||
<td style="padding:0.4rem 0;color:var(--text-muted,#6b7280);font-size:0.9rem;">
|
||||
All devices with installed battery count
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0.4rem 0.75rem 0.4rem 0;">
|
||||
<a href="{{ url_for('export_charge_logs_csv') }}" class="btn btn-sm btn-secondary">charge-logs.csv</a>
|
||||
</td>
|
||||
<td style="padding:0.4rem 0;color:var(--text-muted,#6b7280);font-size:0.9rem;">
|
||||
All charge log entries with battery label
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0.4rem 0.75rem 0.4rem 0;">
|
||||
<a href="{{ url_for('export_capacity_tests_csv') }}" class="btn btn-sm btn-secondary">capacity-tests.csv</a>
|
||||
</td>
|
||||
<td style="padding:0.4rem 0;color:var(--text-muted,#6b7280);font-size:0.9rem;">
|
||||
All capacity test results with battery label
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0.4rem 0.75rem 0.4rem 0;">
|
||||
<a href="{{ url_for('export_pct_logs_csv') }}" class="btn btn-sm btn-secondary">pct-logs.csv</a>
|
||||
</td>
|
||||
<td style="padding:0.4rem 0;color:var(--text-muted,#6b7280);font-size:0.9rem;">
|
||||
Battery percentage history
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:0.75rem;">
|
||||
<a href="{{ url_for('import_page') }}" class="btn btn-secondary">Import JSON</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,73 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Import — Battery Tracker{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 style="margin-bottom:1.25rem;">Import Data</h1>
|
||||
|
||||
<div class="card" style="margin-bottom:1rem;">
|
||||
<p style="color:var(--text-muted,#6b7280);margin-bottom:1rem;">
|
||||
Upload a JSON file exported from Battery Tracker.
|
||||
Devices are matched by name and batteries by label — existing records are skipped (not overwritten).
|
||||
Charge logs, capacity tests, and percentage logs are always appended.
|
||||
</p>
|
||||
<form method="POST" action="{{ url_for('import_page') }}" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="file">JSON Export File</label>
|
||||
<input type="file" id="file" name="file" accept=".json,application/json" required
|
||||
style="width:100%;padding:0.45rem 0.65rem;border:1px solid var(--border-input,#d1d5db);
|
||||
border-radius:4px;font-size:0.9rem;background:var(--bg-input,#fff);
|
||||
color:var(--text-body,#111827);">
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Import</button>
|
||||
<a href="{{ url_for('export_page') }}" class="btn btn-secondary">Back to Export</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if results is not none %}
|
||||
<div class="card">
|
||||
<h2 style="margin-bottom:1rem;">Import Results</h2>
|
||||
<table style="border-collapse:collapse;width:100%;max-width:480px;">
|
||||
<thead>
|
||||
<tr style="border-bottom:2px solid var(--border,#e5e7eb);">
|
||||
<th style="text-align:left;padding:0.4rem 0.75rem 0.4rem 0;font-size:0.85rem;color:var(--text-muted,#6b7280);font-weight:600;">Category</th>
|
||||
<th style="text-align:right;padding:0.4rem 0.75rem;font-size:0.85rem;color:var(--text-muted,#6b7280);font-weight:600;">Created / Appended</th>
|
||||
<th style="text-align:right;padding:0.4rem 0;font-size:0.85rem;color:var(--text-muted,#6b7280);font-weight:600;">Skipped</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom:1px solid var(--border,#e5e7eb);">
|
||||
<td style="padding:0.45rem 0.75rem 0.45rem 0;">Devices</td>
|
||||
<td style="text-align:right;padding:0.45rem 0.75rem;">{{ results.devices_created }}</td>
|
||||
<td style="text-align:right;padding:0.45rem 0;">{{ results.devices_skipped }}</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid var(--border,#e5e7eb);">
|
||||
<td style="padding:0.45rem 0.75rem 0.45rem 0;">Batteries</td>
|
||||
<td style="text-align:right;padding:0.45rem 0.75rem;">{{ results.batteries_created }}</td>
|
||||
<td style="text-align:right;padding:0.45rem 0;">{{ results.batteries_skipped }}</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid var(--border,#e5e7eb);">
|
||||
<td style="padding:0.45rem 0.75rem 0.45rem 0;">Charge Logs</td>
|
||||
<td style="text-align:right;padding:0.45rem 0.75rem;">{{ results.charge_logs_appended }}</td>
|
||||
<td style="text-align:right;padding:0.45rem 0;">{{ results.charge_logs_skipped }}</td>
|
||||
</tr>
|
||||
<tr style="border-bottom:1px solid var(--border,#e5e7eb);">
|
||||
<td style="padding:0.45rem 0.75rem 0.45rem 0;">Capacity Tests</td>
|
||||
<td style="text-align:right;padding:0.45rem 0.75rem;">{{ results.capacity_tests_appended }}</td>
|
||||
<td style="text-align:right;padding:0.45rem 0;">{{ results.capacity_tests_skipped }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0.45rem 0.75rem 0.45rem 0;">% Logs</td>
|
||||
<td style="text-align:right;padding:0.45rem 0.75rem;">{{ results.pct_logs_appended }}</td>
|
||||
<td style="text-align:right;padding:0.45rem 0;">{{ results.pct_logs_skipped }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="margin-top:1.25rem;display:flex;gap:0.75rem;flex-wrap:wrap;">
|
||||
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">Go to Dashboard</a>
|
||||
<a href="{{ url_for('import_page') }}" class="btn btn-secondary">Import Another File</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user