Adding Event History to Detail Pages¶
Audience: frontend developers wiring event history into pages. See also: Event History Viewer (Backend) for the query/handler implementation, and Event History Viewer: How It Works for the end-user view.
This guide shows how to add event history viewing capabilities to any aggregate detail page in Foundry.
Quick Start¶
For most detail pages, use the side panel pattern - it provides the best UX without cluttering the main page.
1. Import the Component¶
<script lang="ts">
import EventHistorySidePanel from '$lib/components/events/event-history-side-panel.svelte';
import { History } from '@lucide/svelte';
</script>
2. Add State Variable¶
<script lang="ts">
// Your existing state
let myAggregate: MyAggregate | null = $state(null);
// Add this for event history
let showEventHistory = $state(false);
</script>
3. Add Button to Page Header¶
Add the button near your existing action buttons (usually near Edit, Delete, etc.):
<div class="flex gap-2">
<!-- Your existing buttons -->
<Button variant="outline" size="sm" onclick={() => showEventHistory = true} disabled={!myAggregate}>
<History class="mr-2 h-4 w-4" />
Event History
</Button>
<Button size="sm" onclick={startEdit}>
<Edit class="mr-2 h-4 w-4" />
Edit
</Button>
</div>
4. Add Side Panel at End of Page¶
Place this after your main content, before the closing </div>:
<!-- Event History Side Panel -->
{#if myAggregate}
<EventHistorySidePanel
aggregateRootId={myAggregate.id}
title="My Aggregate Event History"
bind:open={showEventHistory}
/>
{/if}
Complete Example¶
Here's a complete example based on a participant detail page:
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { getParticipantById, type Participant } from '$lib/utils/query-helpers';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { ChevronLeft, Edit, History } from '@lucide/svelte';
import EventHistorySidePanel from '$lib/components/events/event-history-side-panel.svelte';
const participantId = $derived($page.params.id!);
let participant = $state<Participant | null>(null);
let isLoading = $state(true);
let error = $state<string | null>(null);
let showEventHistory = $state(false);
onMount(async () => {
await loadParticipant();
});
async function loadParticipant() {
try {
isLoading = true;
error = null;
const response = await getParticipantById(participantId);
if (response.data) {
participant = response.data;
} else {
error = 'Participant not found';
}
} catch (err: any) {
error = err.message;
} finally {
isLoading = false;
}
}
function goBack() {
goto('/participants');
}
</script>
<svelte:head>
<title>{participant?.name || 'Participant'} - Foundry</title>
</svelte:head>
<div class="space-y-6">
<!-- Page Header -->
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<Button variant="ghost" size="icon" onclick={goBack}>
<ChevronLeft class="h-5 w-5" />
</Button>
<h2 class="text-3xl font-bold">{participant?.name || 'Participant'}</h2>
</div>
<div class="flex gap-2">
<Button
variant="outline"
size="sm"
onclick={() => showEventHistory = true}
disabled={!participant}
>
<History class="mr-2 h-4 w-4" />
Event History
</Button>
<Button size="sm" disabled={!participant}>
<Edit class="mr-2 h-4 w-4" />
Edit
</Button>
</div>
</div>
<!-- Main Content -->
{#if isLoading}
<div class="flex h-64 items-center justify-center">
<p>Loading...</p>
</div>
{:else if participant}
<Card.Root>
<Card.Header>
<Card.Title>Participant Details</Card.Title>
</Card.Header>
<Card.Content>
<!-- Your participant details here -->
</Card.Content>
</Card.Root>
{/if}
</div>
<!-- Event History Side Panel -->
{#if participant}
<EventHistorySidePanel
aggregateRootId={participant.id}
title="Participant Event History"
bind:open={showEventHistory}
/>
{/if}
Alternative: Tabs Pattern¶
If you prefer showing event history as a permanent tab rather than a button:
<script lang="ts">
import EventHistoryPanel from '$lib/components/events/event-history-panel.svelte';
import * as Tabs from '$lib/components/ui/tabs';
let participant: Participant | null = $state(null);
</script>
<Tabs.Root value="details">
<Tabs.List>
<Tabs.Trigger value="details">Details</Tabs.Trigger>
<Tabs.Trigger value="events">Event History</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="details">
<!-- Your detail content -->
</Tabs.Content>
<Tabs.Content value="events">
{#if participant?.id}
<EventHistoryPanel
aggregateRootId={participant.id}
title="Participant Events"
/>
{/if}
</Tabs.Content>
</Tabs.Root>
Pages to Add Event History¶
Consider adding event history to these aggregate detail pages:
- Competitions - Already implemented
- [ ] Teams -
src/Apps/foundry-web/src/routes/teams/[id]/+page.svelte - [ ] Participants -
src/Apps/foundry-web/src/routes/participants/[id]/+page.svelte - [ ] Sporting Events (Fixtures) -
src/Apps/foundry-web/src/routes/fixtures/[id]/+page.svelte - [ ] Users -
src/Apps/foundry-web/src/routes/admin/users/[id]/+page.svelte
Checklist¶
When adding event history to a page:
- [ ] Import
EventHistorySidePanelcomponent - [ ] Import
Historyicon from@lucide/svelte - [ ] Add
showEventHistorystate variable - [ ] Add "Event History" button to page header
- [ ] Add side panel component at end of page
- [ ] Test opening/closing the panel
- [ ] Test with an aggregate that has events
- [ ] Test search functionality
- [ ] Test download functionality
Tips¶
- Button Placement: Place the "Event History" button near other action buttons, typically before the primary action (Edit)
- Disabled State: Disable the button when the aggregate hasn't loaded yet
- Title: Customize the title to match the aggregate type (e.g., "Competition Event History", "Team Event History")
- Testing: To test, create/update the aggregate and verify events appear in the history panel
Related Documentation¶
- Event History Viewer - Complete event history documentation
- Event Sourcing Guide - Understanding events
- Common Tasks - Other development patterns