feat(ui): E31 S4b — the admin pages behind one rail row, Configuration as a sectioned list, Statistics as tiles and bars
- NavRailGroup: one "Admin" rail row opening a panel of the admin pages (links, so a middle-click still opens a tab); the rail fits 133% zoom without a hidden scroll - sysconfig.SECTIONS orders the config groups under four headings (every group in exactly one, test-held); the page shows them as Settings' side list (Tabs `group` headings); the lone registration group reads "Registration" under "Access" - StatTiles / StatTile / StatBars: the Statistics overview's figures as tiles, the per-operation quota use as horizontal bars (a zero draws none)
This commit is contained in:
@@ -15,7 +15,9 @@ import FilterChipGroup from "./ui/FilterChipGroup";
|
||||
import Tabs from "./ui/Tabs";
|
||||
import { useTabPanel } from "./ui/tabPanel";
|
||||
import { PageToolbar } from "./ui/PageShell";
|
||||
import { Section, SettingRow } from "./ui/form";
|
||||
import { Section } from "./ui/form";
|
||||
import { SectionLabel } from "./ui/Headings";
|
||||
import { StatBars, StatTile, StatTiles } from "./ui/StatTiles";
|
||||
import { useMe } from "../lib/useMe";
|
||||
import { notifyActionError } from "../lib/actionErrors";
|
||||
import { Button } from "./ui/Button";
|
||||
@@ -108,34 +110,40 @@ function Overview({ me }: { me: Me }) {
|
||||
<>
|
||||
<Section title={t("stats.sync.myStatus")}>
|
||||
{s ? (
|
||||
<div className="space-y-1.5 text-sm">
|
||||
<SettingRow label={t("stats.sync.channels")} hint={t("stats.sync.channelsHint")}>
|
||||
{s.channels_total}
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
<StatTiles>
|
||||
<StatTile
|
||||
label={t("stats.sync.channels")}
|
||||
hint={t("stats.sync.channelsHint")}
|
||||
value={formatNumber(s.channels_total, lang)}
|
||||
/>
|
||||
<StatTile
|
||||
label={t("stats.sync.recentSynced")}
|
||||
hint={t("stats.sync.recentSyncedHint")}
|
||||
>
|
||||
{`${s.channels_recent_synced}/${s.channels_total}`}
|
||||
</SettingRow>
|
||||
<SettingRow label={t("stats.sync.fullHistory")} hint={t("stats.sync.fullHistoryHint")}>
|
||||
{`${s.channels_deep_done}/${s.channels_deep_requested}`}
|
||||
</SettingRow>
|
||||
value={`${formatNumber(s.channels_recent_synced, lang)}/${formatNumber(s.channels_total, lang)}`}
|
||||
/>
|
||||
<StatTile
|
||||
label={t("stats.sync.fullHistory")}
|
||||
hint={t("stats.sync.fullHistoryHint")}
|
||||
value={`${formatNumber(s.channels_deep_done, lang)}/${formatNumber(s.channels_deep_requested, lang)}`}
|
||||
/>
|
||||
{s.deep_pending_count > 0 && (
|
||||
<SettingRow
|
||||
<StatTile
|
||||
label={t("stats.sync.fullHistoryEta")}
|
||||
hint={t("stats.sync.fullHistoryEtaHint", { count: s.deep_pending_count })}
|
||||
>
|
||||
{formatEta(t, s.deep_eta_seconds)}
|
||||
</SettingRow>
|
||||
value={formatEta(t, s.deep_eta_seconds)}
|
||||
/>
|
||||
)}
|
||||
<SettingRow label={t("stats.sync.myVideos")} hint={t("stats.sync.myVideosHint")}>
|
||||
{formatNumber(s.my_videos, lang)}
|
||||
</SettingRow>
|
||||
<SettingRow label={t("stats.sync.quotaLeft")} hint={t("stats.sync.quotaLeftHint")}>
|
||||
{formatNumber(s.quota_remaining_today, lang)}
|
||||
</SettingRow>
|
||||
</div>
|
||||
<StatTile
|
||||
label={t("stats.sync.myVideos")}
|
||||
hint={t("stats.sync.myVideosHint")}
|
||||
value={formatNumber(s.my_videos, lang)}
|
||||
/>
|
||||
<StatTile
|
||||
label={t("stats.sync.quotaLeft")}
|
||||
hint={t("stats.sync.quotaLeftHint")}
|
||||
value={formatNumber(s.quota_remaining_today, lang)}
|
||||
/>
|
||||
</StatTiles>
|
||||
) : status.isError ? (
|
||||
<StateMessage tone="error" onRetry={() => status.refetch()}>
|
||||
{t("common.loadError")}
|
||||
@@ -148,31 +156,39 @@ function Overview({ me }: { me: Me }) {
|
||||
<Section title={t("stats.sync.apiUsage")}>
|
||||
{usage.data ? (
|
||||
<>
|
||||
<div className="space-y-1.5 text-sm">
|
||||
<SettingRow label={t("stats.sync.today")} hint={t("stats.sync.todayHint")}>
|
||||
{formatNumber(usage.data.today, lang)}
|
||||
</SettingRow>
|
||||
<SettingRow label={t("stats.sync.last7d")}>
|
||||
{formatNumber(usage.data.last_7d, lang)}
|
||||
</SettingRow>
|
||||
<SettingRow label={t("stats.sync.last30d")}>
|
||||
{formatNumber(usage.data.last_30d, lang)}
|
||||
</SettingRow>
|
||||
<SettingRow label={t("stats.sync.allTime")}>
|
||||
{formatNumber(usage.data.all_time, lang)}
|
||||
</SettingRow>
|
||||
</div>
|
||||
<StatTiles>
|
||||
<StatTile
|
||||
label={t("stats.sync.today")}
|
||||
hint={t("stats.sync.todayHint")}
|
||||
value={formatNumber(usage.data.today, lang)}
|
||||
/>
|
||||
<StatTile
|
||||
label={t("stats.sync.last7d")}
|
||||
value={formatNumber(usage.data.last_7d, lang)}
|
||||
/>
|
||||
<StatTile
|
||||
label={t("stats.sync.last30d")}
|
||||
value={formatNumber(usage.data.last_30d, lang)}
|
||||
/>
|
||||
<StatTile
|
||||
label={t("stats.sync.allTime")}
|
||||
value={formatNumber(usage.data.all_time, lang)}
|
||||
/>
|
||||
</StatTiles>
|
||||
{Object.keys(usage.data.by_action).length > 0 && (
|
||||
<div className="mt-2 pt-2 border-t border-border space-y-1 text-xs text-muted">
|
||||
<div className="uppercase tracking-wide">{t("stats.sync.byAction")}</div>
|
||||
{Object.entries(usage.data.by_action)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([action, units]) => (
|
||||
<div key={action} className="flex justify-between gap-2">
|
||||
<span>{quotaActionLabel(t, action)}</span>
|
||||
<span className="tabular-nums">{formatNumber(units, lang)}</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="mt-4">
|
||||
<SectionLabel level={3} className="mb-2">
|
||||
{t("stats.sync.byAction")}
|
||||
</SectionLabel>
|
||||
<StatBars
|
||||
label={t("stats.sync.byAction")}
|
||||
format={(n) => formatNumber(n, lang)}
|
||||
rows={Object.entries(usage.data.by_action).map(([action, units]) => ({
|
||||
id: action,
|
||||
label: quotaActionLabel(t, action),
|
||||
value: units,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user