fix: align usage heatmap month labels
This commit is contained in:
@@ -126,7 +126,7 @@ function UsageOverviewView({ audience, overview, loading, error, periodDays, onP
|
||||
<section className="usage-activity-card">
|
||||
<header><div><strong>{metricMode === "duration" ? "聊天活动" : "费用活动"}</strong><span>{overview?.scope.device_name || overview?.scope.household_name || overview?.scope.tenant_name || "当前范围"}</span></div><div className="usage-density-tabs">{(["daily", "weekly", "cumulative"] as DensityMode[]).map((mode) => <button type="button" className={densityMode === mode ? "active" : ""} key={mode} onClick={() => setDensityMode(mode)}>{({ daily: "每日", weekly: "每周", cumulative: "累计" } as Record<DensityMode, string>)[mode]}</button>)}</div></header>
|
||||
<div className="usage-heatmap-scroll">
|
||||
<div className="usage-months">{heatmap.months.map((item) => <span key={item.key}>{item.label}</span>)}</div>
|
||||
<div className="usage-months" style={{ gridTemplateColumns: `repeat(${heatmap.weeks}, 11px)` }}>{heatmap.months.map((item) => <span key={item.key} style={{ gridColumnStart: item.week }}>{item.label}</span>)}</div>
|
||||
<div className="usage-heatmap-wrap"><div className="usage-weekdays"><span>一</span><span>三</span><span>五</span></div><div className="usage-heatmap">{heatmap.cells.map((cell) => cell.date ? <button type="button" key={cell.date} className={`level-${cell.level} ${selectedDate === cell.date ? "selected" : ""}`} title={`${cell.date} · ${metricMode === "duration" ? formatDuration(cell.rawDuration ?? 0) : formatMoney(cell.rawCost ?? 0, overview?.currency)} · ${cell.count ?? 0}次`} aria-label={`${cell.date},${cell.count ?? 0}次通话`} onClick={() => setSelectedDate(cell.date)} /> : <span key={cell.key} />)}</div></div>
|
||||
</div>
|
||||
<footer><span>{selectedDay ? `${selectedDay.date}:${selectedDay.conversation_count} 次 · ${formatDuration(selectedDay.duration_ms)}${selectedDay.cost_micros ? ` · ${formatMoney(selectedDay.cost_micros, overview?.currency)}` : ""}` : "点击色块查看当天数据"}</span><div className="usage-legend"><span>少</span>{[0, 1, 2, 3, 4].map((level) => <i className={`level-${level}`} key={level} />)}<span>多</span></div></footer>
|
||||
@@ -163,7 +163,7 @@ function useUsageOverviewRequest(accessToken: string, periodDays: number, load:
|
||||
}
|
||||
|
||||
function buildHeatmap(overview: UsageOverview | undefined, metric: MetricMode, density: DensityMode) {
|
||||
if (!overview) return { cells: [] as HeatCell[], months: [] as Array<{ key: string; label: string }> };
|
||||
if (!overview) return { cells: [] as HeatCell[], months: [] as MonthMarker[], weeks: 0 };
|
||||
const daily = new Map(overview.daily.map((item) => [item.date, item]));
|
||||
const start = dateOnly(new Date(overview.from));
|
||||
const end = dateOnly(new Date(overview.to));
|
||||
@@ -193,15 +193,22 @@ function buildHeatmap(overview: UsageOverview | undefined, metric: MetricMode, d
|
||||
const padding = (start.getDay() + 6) % 7;
|
||||
const cells: HeatCell[] = Array.from({ length: padding }, (_, index) => ({ key: `pad-${index}` }));
|
||||
values.forEach((item) => cells.push({ ...item, key: item.date, level: level(item.value) }));
|
||||
const months: Array<{ key: string; label: string }> = [];
|
||||
values.forEach((item) => {
|
||||
const months: MonthMarker[] = [];
|
||||
values.forEach((item, index) => {
|
||||
const key = item.date.slice(0, 7);
|
||||
if (!months.some((month) => month.key === key)) months.push({ key, label: `${Number(key.slice(5))}月` });
|
||||
if (!months.some((month) => month.key === key)) {
|
||||
months.push({ key, label: `${Number(key.slice(5))}月`, week: Math.floor((padding + index) / 7) + 1 });
|
||||
}
|
||||
});
|
||||
return { cells, months };
|
||||
// A range can start on the last few days of a month. In that case the first
|
||||
// two labels would occupy adjacent week columns and overlap, so retain the
|
||||
// first complete month label instead.
|
||||
if (months.length > 1 && months[1].week - months[0].week < 3) months.shift();
|
||||
return { cells, months, weeks: Math.ceil((padding + values.length) / 7) };
|
||||
}
|
||||
|
||||
type HeatCell = { key: string; date?: string; rawDuration?: number; rawCost?: number; count?: number; value?: number; level?: number };
|
||||
type MonthMarker = { key: string; label: string; week: number };
|
||||
|
||||
function dateOnly(value: Date) { return new Date(value.getFullYear(), value.getMonth(), value.getDate()); }
|
||||
function localDateKey(value: Date) { return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, "0")}-${String(value.getDate()).padStart(2, "0")}`; }
|
||||
|
||||
+2
-1
@@ -3235,7 +3235,8 @@ textarea:focus,
|
||||
.usage-density-tabs button { min-height: 30px !important; padding: 0 7px !important; border-radius: 7px !important; color: #96969c !important; background: transparent !important; font-size: 12px; font-weight: 550; }
|
||||
.usage-density-tabs button.active { color: #25252a !important; background: #f1f1f3 !important; }
|
||||
.usage-heatmap-scroll { overflow-x: auto; padding: 2px 2px 10px; scrollbar-width: thin; }
|
||||
.usage-months { display: flex; justify-content: space-between; min-width: 790px; margin: 0 0 9px 30px; color: #919197; font-size: 10px; }
|
||||
.usage-months { display: grid; grid-auto-rows: 14px; gap: 4px; width: max-content; margin: 0 0 9px 30px; color: #919197; font-size: 10px; }
|
||||
.usage-months span { overflow: visible; white-space: nowrap; }
|
||||
.usage-heatmap-wrap { display: flex; align-items: flex-start; gap: 10px; min-width: 820px; }
|
||||
.usage-weekdays { display: grid; grid-template-rows: repeat(3,18px); gap: 8px; width: 20px; padding-top: 7px; color: #aaaab0; font-size: 9px; }
|
||||
.usage-heatmap { display: grid; grid-auto-flow: column; grid-auto-columns: 11px; grid-template-rows: repeat(7,11px); gap: 4px; width: max-content; min-height: 101px; }
|
||||
|
||||
Reference in New Issue
Block a user