Files
beavergram/frontend/src/lib/components/presence/VolumeChart.svelte
T

162 lines
3.3 KiB
Svelte

<script lang="ts">
import type { VolumeBucket } from "#lib/api/types.js";
import { formatDay, parseDay } from "#lib/format/datetime.js";
interface Props {
volume: VolumeBucket[];
}
const { volume }: Props = $props();
const TEXT = {
bar: (day: string, bucket: VolumeBucket) =>
bucket.total === 0
? `${day}, нет сообщений`
: `${day}, всего ${bucket.total}: входящих ${bucket.incoming}, моих ${bucket.outgoing}`,
incoming: "входящие",
outgoing: "мои",
today: "сегодня",
};
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="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 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;
height: 6rem;
&.dense {
gap: 1px;
}
}
.slot {
display: flex;
flex: 1;
align-items: flex-end;
height: 100%;
}
.bar {
display: flex;
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 {
background-color: var(--color-primary);
}
.seg.in {
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 {
display: flex;
gap: 1rem;
margin-top: 0.5rem;
font-size: 0.75rem;
color: var(--color-text-secondary);
}
.key {
display: flex;
gap: 0.375rem;
align-items: center;
}
.swatch {
width: 0.625rem;
height: 0.625rem;
border-radius: 2px;
&.out {
background-color: var(--color-primary);
}
&.in {
background-color: color-mix(
in srgb,
var(--color-primary) 40%,
transparent
);
}
}
</style>