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:
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>
|
||||
Reference in new issue
Block a user