dynamic bar count (multiples of 28), header overflow fix, parentId index for faster chain lookups
This commit is contained in:
1 parent
e2c7adab90
commit
59c492c04f
3 files changed
+29
-7
No files matched your search
@@ -15,6 +15,14 @@ var app = builder.Build();
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
// Ensure indexes for chain lookups
|
||||
using (var db = app.Services.GetRequiredService<Func<SqliteConnection>>()())
|
||||
{
|
||||
db.Open();
|
||||
db.Execute("CREATE INDEX IF NOT EXISTS idx_entries_parentId ON entries(json_extract(data, '$.parentId'))");
|
||||
}
|
||||
|
||||
|
||||
// GET /api/cost-buckets?from=&to=&bucketMinutes=
|
||||
// Returns N buckets of aggregated cost
|
||||
app.MapGet("/api/cost-buckets", (Func<SqliteConnection> factory, string from, string to, int bucketMinutes) =>
|
||||
|
||||
+17
-5
@@ -11,7 +11,13 @@ const WEEKLY_BUDGET = 500; // $ pro Woche
|
||||
// Anchor config - loaded from /api/settings, defaults: Thursday 23:00
|
||||
let ANCHOR_DAY = 0; // 0=Sun, 1=Mon, ..., 4=Thu
|
||||
let ANCHOR_HOUR = 0;
|
||||
const BAR_COUNT = 50;
|
||||
function getBarCount() {
|
||||
const w = window.innerWidth || document.documentElement.clientWidth;
|
||||
// ~12px per bar minimum, count must be divisible by 14
|
||||
const raw = Math.floor(w / 12);
|
||||
const count = Math.floor(raw / 28) * 28;
|
||||
return Math.max(28, count);
|
||||
}
|
||||
const CHAIN_COLORS = [
|
||||
'#4ade80','#60a5fa','#f97316','#c084fc','#f472b6',
|
||||
'#facc15','#34d399','#818cf8','#fb7185','#a78bfa',
|
||||
@@ -104,6 +110,12 @@ async function init() {
|
||||
|
||||
restoreFromHash();
|
||||
await loadView();
|
||||
|
||||
let resizeTimer;
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(() => loadView(), 250);
|
||||
});
|
||||
}
|
||||
|
||||
function saveToHash() {
|
||||
@@ -147,7 +159,7 @@ async function loadView() {
|
||||
renderEntryChart(entries);
|
||||
} else {
|
||||
const totalMinutes = (viewTo - viewFrom) / 60000;
|
||||
const bucketMinutes = Math.max(1, Math.round(totalMinutes / BAR_COUNT));
|
||||
const bucketMinutes = Math.max(1, Math.round(totalMinutes / getBarCount()));
|
||||
const buckets = await api(`/api/cost-buckets?from=${enc(from)}&to=${enc(to)}&bucketMinutes=${bucketMinutes}`);
|
||||
renderSummary(summary);
|
||||
renderBucketChart(buckets, bucketMinutes);
|
||||
@@ -181,7 +193,7 @@ function renderBucketChart(buckets, bucketMinutes) {
|
||||
let lastWeekId = null;
|
||||
let lastDayId = null;
|
||||
|
||||
for (let i = 0; i < BAR_COUNT; i++) {
|
||||
for (let i = 0; i < getBarCount(); i++) {
|
||||
const b = bucketMap[i];
|
||||
const cost = b ? b.cost : 0;
|
||||
const count = b ? b.count : 0;
|
||||
@@ -226,7 +238,7 @@ function renderBucketChart(buckets, bucketMinutes) {
|
||||
|
||||
const lbl = document.createElement('div');
|
||||
lbl.className = 'chart-label';
|
||||
if (i % Math.ceil(BAR_COUNT / 6) === 0) lbl.textContent = fmtShort(bucketStart);
|
||||
if (i % Math.ceil(getBarCount() / 6) === 0) lbl.textContent = fmtShort(bucketStart);
|
||||
labels.appendChild(lbl);
|
||||
}
|
||||
}
|
||||
@@ -332,7 +344,7 @@ function renderChainBlocks(chains) {
|
||||
// Already at max zoom (1 Tag) - enter entry mode for the bucket containing this chain
|
||||
const chainMid = new Date((new Date(c.first).getTime() + new Date(c.last).getTime()) / 2);
|
||||
const totalMinutes = (viewTo - viewFrom) / 60000;
|
||||
const bucketMinutes = Math.max(1, Math.round(totalMinutes / BAR_COUNT));
|
||||
const bucketMinutes = Math.max(1, Math.round(totalMinutes / getBarCount()));
|
||||
const bucketMs = bucketMinutes * 60000;
|
||||
const elapsed = chainMid - viewFrom;
|
||||
const bucketIndex = Math.floor(elapsed / bucketMs);
|
||||
|
||||
+4
-2
@@ -30,8 +30,12 @@ header {
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
flex-shrink: 0;
|
||||
overflow-x: auto;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
header > * { flex-shrink: 0; }
|
||||
|
||||
header h1 { font-size: 1.1rem; font-weight: 600; color: var(--accent); }
|
||||
|
||||
#anchor-config {
|
||||
@@ -197,8 +201,6 @@ header h1 { font-size: 1.1rem; font-weight: 600; color: var(--accent); }
|
||||
font-size: 0.55rem;
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user