162 lines
3.3 KiB
Svelte
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>
|