feat(frontend): analytics shows minutes online by hour of week and by day, sessions with start and end, says when last seen is hidden, chat status is live and in russian

This commit is contained in:
hh committed 2026-10-19 02:41:39 +00:00
1 parent f44597b2dc
commit 73b8903de1
11 files changed
+1275 -373

No files matched your search

@@ -1,5 +1,6 @@
<script lang="ts">
import type { VolumeBucket } from "#lib/api/types.js";
import { formatDay, parseDay } from "#lib/format/datetime.js";
interface Props {
volume: VolumeBucket[];
@@ -7,47 +8,104 @@
const { volume }: Props = $props();
const max = $derived(Math.max(1, ...volume.map((bucket) => bucket.total)));
const TEXT = {
bar: (day: string, bucket: VolumeBucket) =>
bucket.total === 0
? `${day}, нет сообщений`
: `${day}, всего ${bucket.total}: входящих ${bucket.incoming}, моих ${bucket.outgoing}`,
incoming: "входящие",
outgoing: "мои",
today: "сегодня",
};
function barTitle(bucket: VolumeBucket): string {
return `${bucket.bucket.slice(0, 10)} — всего ${bucket.total} (вх ${bucket.incoming} / исх ${bucket.outgoing})`;
const peak = $derived(Math.max(1, ...volume.map((bucket) => bucket.total)));
const first = $derived(volume[0]);
function label(day: string): string {
return formatDay(parseDay(day).toISOString());
}
</script>
<div class="volume">
<div class="bars">
{#each volume as bucket (bucket.bucket)}
<div
class="bar"
title={barTitle(bucket)}
style:height={`${(bucket.total / max) * 100}%`}
>
<div class="seg out" style:flex-grow={bucket.outgoing}></div>
<div class="seg in" style:flex-grow={bucket.incoming}></div>
</div>
{/each}
<div class="chart">
<span class="peak">{peak}</span>
<div class={["bars", { dense: volume.length > 45 }]}>
{#each volume as bucket (bucket.bucket)}
<div class="slot" title={TEXT.bar(label(bucket.bucket), bucket)}>
<div
class={["bar", { zero: bucket.total === 0 }]}
style:height="{(bucket.total / peak) * 100}%"
>
<div class="seg in" style:flex-grow={bucket.incoming}></div>
<div class="seg out" style:flex-grow={bucket.outgoing}></div>
</div>
</div>
{/each}
</div>
</div>
{#if first}
<div class="axis">
<span>{label(first.bucket)}</span>
<span>{TEXT.today}</span>
</div>
{/if}
<div class="legend">
<span class="key"><span class="swatch out"></span>исходящие</span>
<span class="key"><span class="swatch in"></span>входящие</span>
<span class="key"><span class="swatch in"></span>{TEXT.incoming}</span>
<span class="key"><span class="swatch out"></span>{TEXT.outgoing}</span>
</div>
</div>
<style lang="scss">
.volume {
padding: 0 1rem;
}
.chart {
position: relative;
padding-top: 1.125rem;
border-top: 1px dashed var(--color-borders);
}
.peak {
position: absolute;
top: 0.125rem;
left: 0;
font-size: 0.6875rem;
font-variant-numeric: tabular-nums;
color: var(--color-text-secondary);
}
.bars {
display: flex;
gap: 3px;
align-items: flex-end;
gap: 1px;
height: 7.5rem;
height: 6rem;
&.dense {
gap: 1px;
}
}
.slot {
display: flex;
flex: 1;
align-items: flex-end;
height: 100%;
}
.bar {
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
min-height: 2px;
overflow: hidden;
border-radius: 2px 2px 0 0;
&.zero {
background-color: rgba(var(--color-text-secondary-rgb), 0.3);
}
}
.seg.out {
@@ -55,8 +113,18 @@
}
.seg.in {
background-color: var(--color-text-secondary);
opacity: 0.4;
background-color: color-mix(in srgb, var(--color-primary) 40%, transparent);
}
.axis {
display: flex;
justify-content: space-between;
margin-top: 0.375rem;
font-size: 0.6875rem;
font-variant-numeric: tabular-nums;
color: var(--color-text-secondary);
}
.legend {
@@ -69,8 +137,8 @@
.key {
display: flex;
align-items: center;
gap: 0.375rem;
align-items: center;
}
.swatch {
@@ -83,8 +151,11 @@
}
&.in {
background-color: var(--color-text-secondary);
opacity: 0.4;
background-color: color-mix(
in srgb,
var(--color-primary) 40%,
transparent
);
}
}
</style>