mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 05:06:41 +02:00
style(workbench): polish timeline — time-range filter, event count, prominent revert
The timeline is now the primary AI-review surface post function-
calling migration — a handful of ergonomics tweaks so scanning a
day's AI activity is less friction.
- Time-range toggle (24h / 7T / alle) right-aligned in the filter
row. Default stays `alle` so nothing changes for users who want
everything. Client-side filter — over-fetch already caps at 500.
- Each bucket shows an event-count pill next to the mission title
("8 Änderungen in dieser Iteration"), so the reader sees the
weight of an iteration before expanding.
- Revert button: slightly bigger, label reads "Rückgängig" instead
of "Revert" (matches the rest of the German UI), bold icon, hover
highlights with a softer red tuned to the theme-token palette.
No logic changes to the revert or bucketing code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fabd45bd87
commit
ea673a22c2
1 changed files with 90 additions and 15 deletions
|
|
@ -15,6 +15,7 @@
|
||||||
let moduleFilter = $state<string | null>(null);
|
let moduleFilter = $state<string | null>(null);
|
||||||
let missionFilter = $state<string | null>(null);
|
let missionFilter = $state<string | null>(null);
|
||||||
let agentFilter = $state<string | null>(null);
|
let agentFilter = $state<string | null>(null);
|
||||||
|
let timeRangeFilter = $state<'24h' | '7d' | 'all'>('all');
|
||||||
|
|
||||||
const events = $derived(
|
const events = $derived(
|
||||||
useAiTimeline({
|
useAiTimeline({
|
||||||
|
|
@ -24,12 +25,19 @@
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const allBuckets = $derived(bucketByIteration(events.value));
|
const allBuckets = $derived(bucketByIteration(events.value));
|
||||||
// Agent filter is applied client-side after bucketing because the
|
// Agent + time-range filters are applied client-side after bucketing
|
||||||
// useAiTimeline query is keyed by module/mission only today. If the
|
// because the useAiTimeline query is keyed by module/mission only
|
||||||
// volume ever grows large enough for this to matter, push it into
|
// today. If the volume ever grows large enough for this to matter,
|
||||||
// the query.
|
// push them into the query.
|
||||||
const buckets = $derived(
|
const buckets = $derived(
|
||||||
agentFilter ? allBuckets.filter((b) => b.agentId === agentFilter) : allBuckets
|
allBuckets
|
||||||
|
.filter((b) => (agentFilter ? b.agentId === agentFilter : true))
|
||||||
|
.filter((b) => {
|
||||||
|
if (timeRangeFilter === 'all') return true;
|
||||||
|
const ageMs = Date.now() - new Date(b.firstTimestamp).getTime();
|
||||||
|
const cutoff = timeRangeFilter === '24h' ? 86_400_000 : 7 * 86_400_000;
|
||||||
|
return ageMs <= cutoff;
|
||||||
|
})
|
||||||
);
|
);
|
||||||
const missions = $derived(useMissions());
|
const missions = $derived(useMissions());
|
||||||
const missionTitleById = $derived(new Map(missions.value.map((m) => [m.id, m.title])));
|
const missionTitleById = $derived(new Map(missions.value.map((m) => [m.id, m.title])));
|
||||||
|
|
@ -167,6 +175,21 @@
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
{#if tab === 'timeline'}
|
||||||
|
<div class="range-group" role="tablist" aria-label="Zeitraum">
|
||||||
|
{#each [{ id: '24h', label: '24h' }, { id: '7d', label: '7T' }, { id: 'all', label: 'alle' }] as const as opt}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="range-btn"
|
||||||
|
class:range-active={timeRangeFilter === opt.id}
|
||||||
|
aria-pressed={timeRangeFilter === opt.id}
|
||||||
|
onclick={() => (timeRangeFilter = opt.id)}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if tab === 'audit'}
|
{#if tab === 'audit'}
|
||||||
|
|
@ -228,6 +251,9 @@
|
||||||
<span class="agent-name">{bucketAgent?.name ?? b.agentDisplayName}</span>
|
<span class="agent-name">{bucketAgent?.name ?? b.agentDisplayName}</span>
|
||||||
<span class="mission-sep">·</span>
|
<span class="mission-sep">·</span>
|
||||||
{missionTitleById.get(b.missionId) ?? b.missionId}
|
{missionTitleById.get(b.missionId) ?? b.missionId}
|
||||||
|
<span class="event-count" title="{b.events.length} Änderungen in dieser Iteration"
|
||||||
|
>{b.events.length}</span
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
{#if b.rationale}
|
{#if b.rationale}
|
||||||
<p class="rationale">{b.rationale}</p>
|
<p class="rationale">{b.rationale}</p>
|
||||||
|
|
@ -238,9 +264,10 @@
|
||||||
class="revert"
|
class="revert"
|
||||||
disabled={revertingKey !== null}
|
disabled={revertingKey !== null}
|
||||||
onclick={() => handleRevert(b.key, b.missionId, b.iterationId)}
|
onclick={() => handleRevert(b.key, b.missionId, b.iterationId)}
|
||||||
|
title="Alle Änderungen dieser Iteration zurücknehmen"
|
||||||
>
|
>
|
||||||
<ArrowCounterClockwise size={12} />
|
<ArrowCounterClockwise size={13} weight="bold" />
|
||||||
<span>{revertingKey === b.key ? 'Läuft…' : 'Revert'}</span>
|
<span>{revertingKey === b.key ? 'Läuft…' : 'Rückgängig'}</span>
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
<ul class="events">
|
<ul class="events">
|
||||||
|
|
@ -362,6 +389,43 @@
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
}
|
}
|
||||||
|
.range-group {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 0.125rem;
|
||||||
|
margin-left: auto;
|
||||||
|
background: hsl(var(--color-muted));
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
padding: 0.125rem;
|
||||||
|
}
|
||||||
|
.range-btn {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: hsl(var(--color-muted-foreground));
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
.range-btn:hover {
|
||||||
|
background: hsl(var(--color-surface));
|
||||||
|
}
|
||||||
|
.range-active {
|
||||||
|
background: hsl(var(--color-surface));
|
||||||
|
color: hsl(var(--color-foreground));
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.event-count {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
padding: 0 0.375rem;
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: hsl(var(--color-muted-foreground));
|
||||||
|
background: hsl(var(--color-muted));
|
||||||
|
border-radius: 999px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
.empty {
|
.empty {
|
||||||
color: hsl(var(--color-muted-foreground));
|
color: hsl(var(--color-muted-foreground));
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
|
@ -423,20 +487,31 @@
|
||||||
.revert {
|
.revert {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.25rem;
|
gap: 0.375rem;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.4rem 0.75rem;
|
||||||
border: 1px solid hsl(var(--color-border));
|
border: 1px solid hsl(var(--color-border));
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.375rem;
|
||||||
background: hsl(var(--color-surface));
|
background: hsl(var(--color-surface));
|
||||||
color: hsl(var(--color-muted-foreground));
|
color: hsl(var(--color-foreground));
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-size: 0.6875rem;
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
background 0.15s,
|
||||||
|
border-color 0.15s,
|
||||||
|
color 0.15s;
|
||||||
|
align-self: start;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.revert:hover:not(:disabled) {
|
.revert:hover:not(:disabled) {
|
||||||
color: #8a1b1b;
|
color: hsl(0 72% 45%);
|
||||||
border-color: #e99;
|
border-color: hsl(0 72% 60%);
|
||||||
background: #fff0f0;
|
background: hsl(0 72% 95%);
|
||||||
|
}
|
||||||
|
.revert:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
.events {
|
.events {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue