move anchor config to frontend dropdowns + localStorage, remove /api/settings
This commit is contained in:
1 parent
c245e0e374
commit
e2c7adab90
5 files changed
+66
-17
No files matched your search
@@ -15,14 +15,6 @@ var app = builder.Build();
|
|||||||
app.UseDefaultFiles();
|
app.UseDefaultFiles();
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
// GET /api/settings
|
|
||||||
app.MapGet("/api/settings", (IConfiguration config) =>
|
|
||||||
{
|
|
||||||
var anchorDay = int.TryParse(config["AnchorDay"], out var d) ? d : 0;
|
|
||||||
var anchorHour = int.TryParse(config["AnchorHour"], out var h) ? h : 0;
|
|
||||||
return Results.Ok(new { anchorDay, anchorHour });
|
|
||||||
});
|
|
||||||
|
|
||||||
// GET /api/cost-buckets?from=&to=&bucketMinutes=
|
// GET /api/cost-buckets?from=&to=&bucketMinutes=
|
||||||
// Returns N buckets of aggregated cost
|
// Returns N buckets of aggregated cost
|
||||||
app.MapGet("/api/cost-buckets", (Func<SqliteConnection> factory, string from, string to, int bucketMinutes) =>
|
app.MapGet("/api/cost-buckets", (Func<SqliteConnection> factory, string from, string to, int bucketMinutes) =>
|
||||||
|
|||||||
+1
-3
@@ -7,7 +7,5 @@
|
|||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Urls": "http://0.0.0.0:18684",
|
"Urls": "http://0.0.0.0:18684",
|
||||||
"DbPath": "../jsonl-sync/sessions.db",
|
"DbPath": "../jsonl-sync/sessions.db"
|
||||||
"AnchorDay": 0,
|
|
||||||
"AnchorHour": 0
|
|
||||||
}
|
}
|
||||||
+39
-6
@@ -68,12 +68,34 @@ async function init() {
|
|||||||
$('modal').addEventListener('click', e => { if (e.target === $('modal')) closeModal(); });
|
$('modal').addEventListener('click', e => { if (e.target === $('modal')) closeModal(); });
|
||||||
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); });
|
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); });
|
||||||
|
|
||||||
// Load anchor settings
|
// Anchor dropdowns
|
||||||
try {
|
const daySelect = $('anchor-day');
|
||||||
const settings = await api('/api/settings');
|
const hourSelect = $('anchor-hour');
|
||||||
if (settings.anchorDay != null) ANCHOR_DAY = settings.anchorDay;
|
for (let h = 0; h < 24; h++) {
|
||||||
if (settings.anchorHour != null) ANCHOR_HOUR = settings.anchorHour;
|
const opt = document.createElement('option');
|
||||||
} catch {}
|
opt.value = h;
|
||||||
|
opt.textContent = h.toString().padStart(2, '0') + ':00';
|
||||||
|
hourSelect.appendChild(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load from localStorage
|
||||||
|
const savedDay = localStorage.getItem('anchorDay');
|
||||||
|
const savedHour = localStorage.getItem('anchorHour');
|
||||||
|
if (savedDay != null) ANCHOR_DAY = parseInt(savedDay);
|
||||||
|
if (savedHour != null) ANCHOR_HOUR = parseInt(savedHour);
|
||||||
|
daySelect.value = ANCHOR_DAY;
|
||||||
|
hourSelect.value = ANCHOR_HOUR;
|
||||||
|
|
||||||
|
daySelect.addEventListener('change', () => {
|
||||||
|
ANCHOR_DAY = parseInt(daySelect.value);
|
||||||
|
localStorage.setItem('anchorDay', ANCHOR_DAY);
|
||||||
|
resetToStart();
|
||||||
|
});
|
||||||
|
hourSelect.addEventListener('change', () => {
|
||||||
|
ANCHOR_HOUR = parseInt(hourSelect.value);
|
||||||
|
localStorage.setItem('anchorHour', ANCHOR_HOUR);
|
||||||
|
resetToStart();
|
||||||
|
});
|
||||||
|
|
||||||
zoomIndex = 0;
|
zoomIndex = 0;
|
||||||
const range = getAnchoredRange(zoomIndex);
|
const range = getAnchoredRange(zoomIndex);
|
||||||
@@ -445,6 +467,17 @@ function enterEntryMode(bucketStart, bucketEnd) {
|
|||||||
loadView();
|
loadView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetToStart() {
|
||||||
|
location.hash = '';
|
||||||
|
zoomIndex = 0;
|
||||||
|
entryMode = false;
|
||||||
|
zoomStack = [];
|
||||||
|
const range = getAnchoredRange(zoomIndex);
|
||||||
|
viewFrom = range.from;
|
||||||
|
viewTo = range.to;
|
||||||
|
loadView();
|
||||||
|
}
|
||||||
|
|
||||||
function zoomOut() {
|
function zoomOut() {
|
||||||
if (zoomStack.length) {
|
if (zoomStack.length) {
|
||||||
const prev = zoomStack.pop();
|
const prev = zoomStack.pop();
|
||||||
|
|||||||
@@ -10,6 +10,15 @@
|
|||||||
<header>
|
<header>
|
||||||
<h1>Cost Analytics</h1>
|
<h1>Cost Analytics</h1>
|
||||||
<div id="summary"></div>
|
<div id="summary"></div>
|
||||||
|
<div id="anchor-config">
|
||||||
|
<label>Anker:</label>
|
||||||
|
<select id="anchor-day">
|
||||||
|
<option value="0">So</option><option value="1">Mo</option><option value="2">Di</option>
|
||||||
|
<option value="3">Mi</option><option value="4">Do</option><option value="5">Fr</option>
|
||||||
|
<option value="6">Sa</option>
|
||||||
|
</select>
|
||||||
|
<select id="anchor-hour"></select>
|
||||||
|
</div>
|
||||||
<button id="btn-sync" title="Neue Einträge importieren">↻ Sync</button>
|
<button id="btn-sync" title="Neue Einträge importieren">↻ Sync</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,23 @@ header {
|
|||||||
|
|
||||||
header h1 { font-size: 1.1rem; font-weight: 600; color: var(--accent); }
|
header h1 { font-size: 1.1rem; font-weight: 600; color: var(--accent); }
|
||||||
|
|
||||||
|
#anchor-config {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
#anchor-config select {
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
#btn-sync {
|
#btn-sync {
|
||||||
background: var(--border);
|
background: var(--border);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
|||||||
Reference in new issue
Block a user