pc tracker: Megekko new build (9950X3D/X870E), dated ordered/leaving badges, future slider
All checks were successful
Deploy production / deploy (push) Successful in 1m15s

This commit is contained in:
2026-10-25 23:59:15 +00:00
parent cf0fec973c
commit 3d3f008037
33 changed files with 166 additions and 12 deletions

View File

@@ -2,6 +2,7 @@
import { enhance } from '$app/forms';
import { replaceState } from '$app/navigation';
import { page } from '$app/state';
import { untrack } from 'svelte';
import { flip } from 'svelte/animate';
import { fade } from 'svelte/transition';
import Plus from 'lucide-svelte/icons/plus';
@@ -13,6 +14,7 @@
import Sparkles from 'lucide-svelte/icons/sparkles';
import Trash2 from 'lucide-svelte/icons/trash-2';
import EyeOff from 'lucide-svelte/icons/eye-off';
import Truck from 'lucide-svelte/icons/truck';
import EditDialogs from '#lib/pc/EditDialogs.svelte';
import Lightbox from '#lib/pc/Lightbox.svelte';
import PcIcon from '#lib/pc/PcIcon.svelte';
@@ -69,9 +71,21 @@
])
);
const minDay = $derived(events.find((e) => e.date <= today)?.date ?? today);
// Ordered parts have a future install date: the slider runs on to that day so the new build
// can be previewed; "today" stays the default.
const maxDay = $derived(events.reduce((m, e) => (e.date > m ? e.date : m), today));
const incoming = $derived(
parts.filter((p) => p.orderDate && p.installedAt && p.installedAt > today && !isUndatedPast(p))
);
const incomingDay = $derived(
incoming.reduce<string | null>(
(m, p) => (m === null || p.installedAt! < m ? p.installedAt : m),
null
)
);
// Read ?at= during SSR too, so a shared link renders that day without a flash.
const q = page.url.searchParams.get('at');
let at = $state(isDay(q) && q <= today ? q : today);
let at = $state(isDay(q) && q <= untrack(() => maxDay) ? q : today);
let urlTimer: ReturnType<typeof setTimeout> | undefined;
let mounted = false;
$effect(() => {
@@ -274,7 +288,22 @@
</header>
<div class="sticky top-0 z-40 -mx-1 mb-3 px-1 pt-1">
<TimeSlider min={minDay} max={today} {events} bind:value={at} />
<TimeSlider min={minDay} max={maxDay} {today} {events} bind:value={at} />
{#if incomingDay && at < incomingDay}
<p class="incoming">
<Truck size={14} />
<span
>Ordered, arriving {shortDate(incomingDay)}: {incoming.map(displayName).join(', ')}.</span
>
<button type="button" onclick={() => (at = incomingDay)}>preview the new build</button>
</p>
{:else if at > today}
<p class="incoming">
<Truck size={14} />
<span>Preview of {shortDate(at)}: parts marked "ordered" have not arrived yet.</span>
<button type="button" onclick={() => (at = today)}>back to today</button>
</p>
{/if}
</div>
<div class="top">
@@ -950,4 +979,36 @@
.link:hover {
text-decoration: underline;
}
.incoming {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.35rem 0.5rem;
margin-top: 0.4rem;
padding: 0.4rem 0.8rem;
border: 1px dashed color-mix(in srgb, #db2777 55%, var(--site-border));
border-radius: 0.8rem;
background: color-mix(in srgb, var(--site-surface) 92%, transparent);
backdrop-filter: blur(10px);
font-size: 0.78rem;
color: var(--site-muted);
}
.incoming :global(svg) {
color: #db2777;
flex-shrink: 0;
}
.incoming span {
flex: 1 1 16rem;
min-width: 0;
}
.incoming button {
border: 1px solid var(--site-border);
border-radius: 0.6rem;
padding: 0.15rem 0.55rem;
color: var(--site-text);
transition: background 0.15s;
}
.incoming button:hover {
background: var(--site-hover);
}
</style>