mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-10-22 05:54:10 -04:00
first pass analytics redo
This commit is contained in:
1 parent
b656fd04c0
commit
45ffa39f2a
69 files changed
+6400
-1138
No files matched your search
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import Container from '$lib/components/Container.svelte';
|
||||
import { SETTINGS_DEFAULTS, type SettingsKey, type StatsSettings } from '$lib/types/stats';
|
||||
|
||||
interface Props {
|
||||
settings: StatsSettings;
|
||||
stale?: boolean;
|
||||
}
|
||||
let { settings, stale = false }: Props = $props();
|
||||
|
||||
const FIELDS: { key: SettingsKey; label: string; hint: string; suffix?: string }[] = [
|
||||
{ key: 'core_plays_per_week', label: 'Core player threshold', hint: 'Finishes in 7 days to count as core.' },
|
||||
{ key: 'at_risk_days', label: 'At-risk after (days)', hint: 'Days inactive before a player is at risk.' },
|
||||
{ key: 'churn_days', label: 'Churned after (days)', hint: 'Days inactive before a player is churned.' },
|
||||
{ key: 'resurrect_gap_days', label: 'Resurrection gap (days)', hint: 'Gap that makes a return "resurrected".' },
|
||||
{ key: 'active_window_days', label: 'Player-state window (days)', hint: 'How much player history is stored.' },
|
||||
{ key: 'd1_target', label: 'D1 return target', hint: 'Target for next-day return.', suffix: '%' },
|
||||
{ key: 'return7_target', label: '7-day return target', hint: 'Target for any return within 7 days.', suffix: '%' },
|
||||
{ key: 'completion_target', label: 'Completion target', hint: 'Started → finished target.', suffix: '%' },
|
||||
{ key: 'first_try_min', label: 'First-try band (min)', hint: 'Below this is too hard.', suffix: '%' },
|
||||
{ key: 'first_try_max', label: 'First-try band (max)', hint: 'Above this is too easy.', suffix: '%' },
|
||||
{ key: 'share_target', label: 'Share target', hint: 'Shares ÷ finishes target.', suffix: '%' },
|
||||
{ key: 'anomaly_mad_threshold', label: 'Anomaly sensitivity', hint: 'Lower = more alerts.' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<details class="group">
|
||||
<summary
|
||||
class="cursor-pointer list-none rounded-xl border border-white/10 bg-white/5 px-4 py-2 text-sm font-semibold text-gray-200 hover:bg-white/10"
|
||||
>
|
||||
⚙️ Settings & thresholds
|
||||
{#if stale}
|
||||
<span class="ml-2 text-xs font-normal text-amber-300"
|
||||
>(snapshot definitions are outdated)</span
|
||||
>
|
||||
{/if}
|
||||
</summary>
|
||||
<Container class="mt-3 p-4 w-full items-stretch text-left gap-3">
|
||||
<p class="text-xs text-gray-400">
|
||||
Changing these re-reads history; snapshots themselves are unchanged.
|
||||
</p>
|
||||
<form method="POST" action="?/updateSettings" class="grid gap-3 sm:grid-cols-2">
|
||||
{#each FIELDS as field (field.key)}
|
||||
<label class="flex flex-col gap-1 text-xs text-gray-300">
|
||||
<span class="font-semibold">{field.label}{field.suffix ? ` (${field.suffix})` : ''}</span>
|
||||
<input
|
||||
type="number"
|
||||
name={field.key}
|
||||
value={settings[field.key]}
|
||||
step="any"
|
||||
min={field.key === 'anomaly_mad_threshold' ? 0.5 : 0}
|
||||
class="rounded-lg border border-white/10 bg-black/30 px-2 py-1.5 text-sm text-gray-100 focus:border-blue-400 focus:outline-none"
|
||||
/>
|
||||
<span class="text-[11px] text-gray-500">{field.hint}</span>
|
||||
</label>
|
||||
{/each}
|
||||
<div class="flex items-center gap-2 sm:col-span-2">
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-500"
|
||||
>
|
||||
Save settings
|
||||
</button>
|
||||
<span class="text-[11px] text-gray-500">
|
||||
Defaults come from <code>SETTINGS_DEFAULTS</code> / <code>STATS_*</code> env vars.
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</Container>
|
||||
</details>
|
||||
Reference in new issue
Block a user