claude-usage-dashboard 1.3.11 → 1.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-usage-dashboard",
3
- "version": "1.3.11",
3
+ "version": "1.3.12",
4
4
  "description": "Dashboard that visualizes Claude Code usage from local session logs",
5
5
  "main": "server/index.js",
6
6
  "bin": {
package/public/js/app.js CHANGED
@@ -47,18 +47,40 @@ function getTimezoneAbbr() {
47
47
  return tz ? tz.value : '';
48
48
  }
49
49
 
50
+ // Derive the 7-day quota window from resets_at, truncated to the hour
51
+ function getQuotaWindow(sevenDay) {
52
+ if (!sevenDay?.resets_at) return null;
53
+ const resetsAt = new Date(sevenDay.resets_at);
54
+ resetsAt.setMinutes(0, 0, 0);
55
+ const windowStart = new Date(resetsAt);
56
+ windowStart.setDate(windowStart.getDate() - 7);
57
+ return { from: windowStart, to: resetsAt };
58
+ }
59
+
50
60
  async function loadQuota() {
51
61
  try {
52
- const today = new Date();
53
- const sevenDaysAgo = new Date(today);
54
- sevenDaysAgo.setDate(today.getDate() - 7);
55
- const fmt = d => d.toISOString().slice(0, 10);
62
+ const data = await fetchQuota();
63
+
64
+ // Use the actual quota window (resets_at - 7 days → resets_at)
65
+ let cost7dValue = 0;
66
+ let quotaWindowFrom = null;
67
+ let quotaWindowTo = null;
68
+ const sevenDay = data.seven_day;
69
+ const window = getQuotaWindow(sevenDay);
70
+ if (window && sevenDay.utilization > 0) {
71
+ quotaWindowFrom = window.from;
72
+ quotaWindowTo = window.to;
73
+ const cost7d = await fetchCost({
74
+ from: window.from.toISOString(),
75
+ to: window.to.toISOString(),
76
+ plan: state.plan.plan,
77
+ });
78
+ cost7dValue = cost7d.api_equivalent_cost_usd;
79
+ }
56
80
 
57
- const [data, cost7d] = await Promise.all([
58
- fetchQuota(),
59
- fetchCost({ from: fmt(sevenDaysAgo), to: fmt(today), plan: state.plan.plan }),
60
- ]);
61
- renderQuotaGauges(document.getElementById('chart-quota'), data, { cost7d: cost7d.api_equivalent_cost_usd });
81
+ renderQuotaGauges(document.getElementById('chart-quota'), data, {
82
+ cost7d: cost7dValue, quotaWindowFrom, quotaWindowTo,
83
+ });
62
84
  const el = document.getElementById('quota-last-updated');
63
85
  if (el && data.lastFetched) el.textContent = `Updated ${new Date(data.lastFetched).toLocaleTimeString()} ${getTimezoneAbbr()}`;
64
86
  } catch { /* silently degrade */ }
@@ -265,9 +287,18 @@ function init() {
265
287
  }
266
288
  }).catch(() => {});
267
289
 
268
- loadAll();
269
- loadQuota();
270
- startAutoRefresh();
290
+ // Default date range to the 7-day quota window (resets_at - 7 days → resets_at)
291
+ fetchQuota().then(data => {
292
+ const window = getQuotaWindow(data.seven_day);
293
+ if (window) {
294
+ const fmt = d => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
295
+ datePicker.setRange(fmt(window.from), fmt(window.to));
296
+ }
297
+ }).catch(() => {}).finally(() => {
298
+ loadAll();
299
+ loadQuota();
300
+ startAutoRefresh();
301
+ });
271
302
  }
272
303
 
273
304
  document.addEventListener('DOMContentLoaded', init);
@@ -95,9 +95,18 @@ export function renderQuotaGauges(container, data, opts = {}) {
95
95
  const proj = document.createElement('div');
96
96
  proj.style.cssText = 'margin-top:12px;padding:8px 12px;background:#1e293b;border-radius:6px;font-size:12px;color:#94a3b8';
97
97
  const monthlyProjected = projectedCost * (30 / 7);
98
+
99
+ // Format the quota window range for display
100
+ const dtFmt = { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' };
101
+ const tzParts = new Intl.DateTimeFormat(undefined, { timeZoneName: 'short' }).formatToParts(new Date());
102
+ const tz = tzParts.find(p => p.type === 'timeZoneName')?.value || '';
103
+ const windowRange = opts.quotaWindowFrom && opts.quotaWindowTo
104
+ ? ` <span style="color:#64748b">(${opts.quotaWindowFrom.toLocaleString(undefined, dtFmt)} → ${opts.quotaWindowTo.toLocaleString(undefined, dtFmt)} ${tz})</span>`
105
+ : '';
106
+
98
107
  proj.innerHTML =
99
- `7-day usage: <strong style="color:#e2e8f0">$${opts.cost7d.toFixed(2)}</strong> API cost at <strong style="color:#e2e8f0">${sevenDay.utilization.toFixed(1)}%</strong> quota` +
100
- ` · Projected at 100%: <strong style="color:#fbbf24">$${projectedCost.toFixed(2)}</strong>/week` +
108
+ `7-day usage: <strong style="color:#e2e8f0">$${opts.cost7d.toFixed(2)}</strong> API cost at <strong style="color:#e2e8f0">${sevenDay.utilization.toFixed(1)}%</strong> quota${windowRange}` +
109
+ `<br>Projected at 100%: <strong style="color:#fbbf24">$${projectedCost.toFixed(2)}</strong>/week` +
101
110
  ` · <strong style="color:#fbbf24">$${monthlyProjected.toFixed(2)}</strong>/month`;
102
111
  container.appendChild(proj);
103
112
  }
@@ -2,9 +2,13 @@ import { calculateRecordCost, getModelPricing } from './pricing.js';
2
2
 
3
3
  export function filterByDateRange(records, from, to) {
4
4
  if (!from && !to) return records;
5
- // Use local time boundaries (no Z suffix = local timezone)
6
- const start = from ? new Date(from + 'T00:00:00.000').getTime() : -Infinity;
7
- const end = to ? new Date(to + 'T23:59:59.999').getTime() : Infinity;
5
+ // Support both date-only (YYYY-MM-DD → local time boundaries) and full ISO timestamps
6
+ const start = from
7
+ ? new Date(from.includes('T') ? from : from + 'T00:00:00.000').getTime()
8
+ : -Infinity;
9
+ const end = to
10
+ ? new Date(to.includes('T') ? to : to + 'T23:59:59.999').getTime()
11
+ : Infinity;
8
12
  return records.filter(r => {
9
13
  const t = new Date(r.timestamp).getTime();
10
14
  return t >= start && t <= end;