feat(frontend): analytics panel and online statuses speak russian and english with real plurals
This commit is contained in:
1 parent
543e6eaa50
commit
cff493f5dd
9 files changed
+286
-190
No files matched your search
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { PresenceHour } from "#lib/api/types.js";
|
||||
import { formatSpan } from "#lib/format/presence.js";
|
||||
import { formatSpan, weekdayLabels } from "#lib/format/presence.js";
|
||||
import { t } from "#lib/i18n/locale.svelte.js";
|
||||
|
||||
interface Props {
|
||||
week: PresenceHour[];
|
||||
@@ -8,28 +9,14 @@
|
||||
|
||||
const { week }: Props = $props();
|
||||
|
||||
const TEXT = {
|
||||
cell: (day: string, cell: PresenceHour) => {
|
||||
const slot = `${day}, ${cell.hour}:00`;
|
||||
if (cell.hours === 0) {
|
||||
return slot;
|
||||
}
|
||||
if (cell.online_minutes === null) {
|
||||
return `${slot}, нет данных`;
|
||||
}
|
||||
return `${slot}, в среднем ${formatSpan(cell.online_minutes * 60)} в сети`;
|
||||
},
|
||||
days: ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"],
|
||||
empty: "нет данных",
|
||||
max: (minutes: number) => `до ${formatSpan(minutes * 60)}`,
|
||||
};
|
||||
|
||||
const HOURS_IN_DAY = 24;
|
||||
const SECONDS_IN_MINUTE = 60;
|
||||
const HOUR_TICKS = [0, 6, 12, 18];
|
||||
const MIN_SHADE = 0.18;
|
||||
|
||||
const weekdays = $derived(weekdayLabels());
|
||||
const rows = $derived(
|
||||
TEXT.days.map((_, weekday) =>
|
||||
weekdays.map((_, weekday) =>
|
||||
week
|
||||
.filter((cell) => cell.weekday === weekday)
|
||||
.sort((a, b) => a.hour - b.hour)
|
||||
@@ -44,6 +31,20 @@
|
||||
return cell.hours > 0 && cell.online_minutes === null;
|
||||
}
|
||||
|
||||
function title(day: string, cell: PresenceHour): string {
|
||||
const slot = `${day}, ${cell.hour}:00`;
|
||||
if (cell.hours === 0) {
|
||||
return slot;
|
||||
}
|
||||
if (cell.online_minutes === null) {
|
||||
return t("presence.cellNoData", { slot });
|
||||
}
|
||||
return t("presence.cellAverage", {
|
||||
slot,
|
||||
span: formatSpan(cell.online_minutes * SECONDS_IN_MINUTE),
|
||||
});
|
||||
}
|
||||
|
||||
function shade(minutes: number | null): number {
|
||||
if (minutes === null || minutes === 0 || peak === 0) {
|
||||
return 0;
|
||||
@@ -55,7 +56,7 @@
|
||||
<div class="heatmap">
|
||||
{#each rows as cells, weekday (weekday)}
|
||||
<div class="row">
|
||||
<span class="day">{TEXT.days[weekday]}</span>
|
||||
<span class="day">{weekdays[weekday]}</span>
|
||||
<div class="cells">
|
||||
{#each cells as cell (cell.hour)}
|
||||
<span
|
||||
@@ -67,7 +68,7 @@
|
||||
zero: cell.online_minutes === 0,
|
||||
},
|
||||
]}
|
||||
title={TEXT.cell(TEXT.days[weekday] ?? "", cell)}
|
||||
title={title(weekdays[weekday] ?? "", cell)}
|
||||
style:--shade="{shade(cell.online_minutes) * 100}%"
|
||||
></span>
|
||||
{/each}
|
||||
@@ -87,13 +88,13 @@
|
||||
{#if peak > 0}
|
||||
<span class="key">
|
||||
<span class="scale"></span>
|
||||
{TEXT.max(peak)}
|
||||
{t("presence.max", { span: formatSpan(peak * SECONDS_IN_MINUTE) })}
|
||||
</span>
|
||||
{/if}
|
||||
{#if hasGaps}
|
||||
<span class="key">
|
||||
<span class="swatch empty"></span>
|
||||
{TEXT.empty}
|
||||
{t("presence.noData")}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -115,7 +116,7 @@
|
||||
|
||||
.day {
|
||||
flex-shrink: 0;
|
||||
width: 1.375rem;
|
||||
width: 1.75rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
@@ -173,7 +174,7 @@
|
||||
gap: 0.25rem 0.75rem;
|
||||
align-items: center;
|
||||
|
||||
margin: 0.375rem 0 0 1.875rem;
|
||||
margin: 0.375rem 0 0 2.25rem;
|
||||
|
||||
font-size: 0.75rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
Reference in new issue
Block a user