chain blocks: single-line layout, user message display, raw json fix, range label with time
This commit is contained in:
1 parent
791dd5718c
commit
670c5b7f16
4 files changed
+160
-52
No files matched your search
+38
-36
@@ -66,33 +66,28 @@ async function loadView() {
|
||||
const to = viewTo.toISOString();
|
||||
|
||||
$('range-label').textContent =
|
||||
`${fmtDate(viewFrom)} - ${fmtDate(viewTo)} (${entryMode ? 'Einträge' : ZOOM_LEVELS[zoomIndex].name})`;
|
||||
`${fmtDateTime(viewFrom)} - ${fmtDateTime(viewTo)} (${entryMode ? 'Einträge' : ZOOM_LEVELS[zoomIndex].name})`;
|
||||
$('btn-zoom-out').disabled = zoomStack.length === 0 && zoomIndex === 0 && !entryMode;
|
||||
|
||||
const [summary, chains] = await Promise.all([
|
||||
api(`/api/cost-summary?from=${enc(from)}&to=${enc(to)}`),
|
||||
api(`/api/cost-chains?from=${enc(from)}&to=${enc(to)}`),
|
||||
]);
|
||||
|
||||
if (entryMode) {
|
||||
const [entries, summary] = await Promise.all([
|
||||
api(`/api/entry-chains?from=${enc(from)}&to=${enc(to)}`),
|
||||
api(`/api/cost-summary?from=${enc(from)}&to=${enc(to)}`),
|
||||
]);
|
||||
const entries = await api(`/api/entry-chains?from=${enc(from)}&to=${enc(to)}`);
|
||||
renderSummary(summary);
|
||||
renderEntryChart(entries);
|
||||
renderEntryList(entries);
|
||||
} else {
|
||||
const totalMinutes = (viewTo - viewFrom) / 60000;
|
||||
const bucketMinutes = Math.max(1, Math.round(totalMinutes / BAR_COUNT));
|
||||
|
||||
const [buckets, summary] = await Promise.all([
|
||||
api(`/api/cost-buckets?from=${enc(from)}&to=${enc(to)}&bucketMinutes=${bucketMinutes}`),
|
||||
api(`/api/cost-summary?from=${enc(from)}&to=${enc(to)}`),
|
||||
]);
|
||||
|
||||
const chains = await api(`/api/cost-chains?from=${enc(from)}&to=${enc(to)}`);
|
||||
|
||||
const buckets = await api(`/api/cost-buckets?from=${enc(from)}&to=${enc(to)}&bucketMinutes=${bucketMinutes}`);
|
||||
renderSummary(summary);
|
||||
renderBucketChart(buckets, bucketMinutes);
|
||||
renderChainBlocks(chains);
|
||||
}
|
||||
|
||||
renderChainBlocks(chains);
|
||||
|
||||
saveToHash();
|
||||
}
|
||||
|
||||
@@ -127,7 +122,7 @@ function renderBucketChart(buckets, bucketMinutes) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'bar-wrapper';
|
||||
wrapper.innerHTML = `
|
||||
<div class="bar-tooltip">${pctScaled(cost)} / ${count}</div>
|
||||
<div class="bar-tooltip">${pct(cost)} / ${count}</div>
|
||||
<div class="bar" style="height:${Math.max(barPct, cost > 0 ? 2 : 0)}%"></div>
|
||||
`;
|
||||
|
||||
@@ -228,26 +223,20 @@ function renderChainBlocks(chains) {
|
||||
chains.forEach((c, ci) => {
|
||||
const block = document.createElement('div');
|
||||
block.className = 'block';
|
||||
const first = new Date(c.first);
|
||||
const last = new Date(c.last);
|
||||
const durSec = Math.round((last - first) / 1000);
|
||||
block.innerHTML = `
|
||||
<div class="block-header">
|
||||
<span class="block-label">${fmtShort(new Date(c.first))} - ${fmtShort(new Date(c.last))}</span>
|
||||
<span class="block-stats">
|
||||
<span class="block-count">${c.count} Einträge</span>
|
||||
<span class="block-cost">${pct(c.totalCost)}</span>
|
||||
</span>
|
||||
<span class="block-date">${fmtDateTime(first)}</span>
|
||||
<span class="block-dur">${durSec}s</span>
|
||||
<span class="block-msg">${escHtml(c.userMessage || '-')}</span>
|
||||
<span class="block-count">${c.count}</span>
|
||||
<span class="block-cost">${pct(c.totalCost)}</span>
|
||||
</div>
|
||||
`;
|
||||
block.querySelector('.block-header').addEventListener('click', () => {
|
||||
// Jump directly to entry mode, centered on this chain
|
||||
const first = new Date(c.first);
|
||||
const last = new Date(c.last);
|
||||
const pad = Math.max((last - first) * 0.5, 60000); // min 1 min padding
|
||||
zoomStack.push({ from: new Date(viewFrom), to: new Date(viewTo), zoom: zoomIndex, entry: false });
|
||||
viewFrom = new Date(first.getTime() - pad);
|
||||
viewTo = new Date(last.getTime() + pad);
|
||||
zoomIndex = ZOOM_LEVELS.length - 1;
|
||||
entryMode = true;
|
||||
loadView();
|
||||
openModal(null, null, c.chainId);
|
||||
});
|
||||
container.appendChild(block);
|
||||
});
|
||||
@@ -372,19 +361,22 @@ async function syncData() {
|
||||
}
|
||||
|
||||
// --- Modal ---
|
||||
async function openModal(dataStr, clickedId) {
|
||||
async function openModal(dataStr, clickedId, chainId) {
|
||||
const chainEl = $('modal-chain');
|
||||
const rawEl = $('modal-content');
|
||||
|
||||
try { rawEl.textContent = JSON.stringify(JSON.parse(dataStr), null, 2); }
|
||||
catch { rawEl.textContent = dataStr; }
|
||||
if (dataStr) {
|
||||
try { rawEl.textContent = JSON.stringify(JSON.parse(dataStr), null, 2); }
|
||||
catch { rawEl.textContent = dataStr; }
|
||||
} else {
|
||||
rawEl.textContent = '';
|
||||
}
|
||||
|
||||
chainEl.innerHTML = '<p style="color:var(--muted)">Lade Kontext...</p>';
|
||||
$('modal').classList.remove('hidden');
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(dataStr);
|
||||
const entryId = parsed.id;
|
||||
const entryId = chainId || JSON.parse(dataStr).id;
|
||||
const chain = await api(`/api/entry-chain/${encodeURIComponent(entryId)}`);
|
||||
|
||||
// Calculate chain total
|
||||
@@ -397,6 +389,11 @@ async function openModal(dataStr, clickedId) {
|
||||
const chainTotal = chainCosts.reduce((a, b) => a + b, 0);
|
||||
const chainCount = chainCosts.filter(c => c > 0).length;
|
||||
|
||||
// Fill raw JSON if not set (e.g. opened from chain block)
|
||||
if (!dataStr) {
|
||||
rawEl.textContent = JSON.stringify(chain.map(e => JSON.parse(e.data)), null, 2);
|
||||
}
|
||||
|
||||
let html = `<div class="chain-summary">
|
||||
<span class="chain-summary-cost">Kette: ${pct(chainTotal)}</span>
|
||||
<span class="chain-summary-count">${chainCount} Einträge mit Kosten / ${chain.length} gesamt</span>
|
||||
@@ -461,6 +458,11 @@ function fmtShort(d) {
|
||||
+ ' ' + d.toLocaleTimeString('de-AT', { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
function fmtDateTime(d) {
|
||||
return d.toLocaleDateString('de-AT', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||
+ ' ' + d.toLocaleTimeString('de-AT', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
}
|
||||
|
||||
function fmtRange(a, b) { return fmtShort(a) + ' - ' + fmtShort(b); }
|
||||
|
||||
function fmtTime(ts) {
|
||||
|
||||
Reference in new issue
Block a user