Skip to content

Commit b507c13

Browse files
TuTiDoreLucHeartCopilot
committed
feat: add shocker logs page
fix: wording Co-authored-by: LucHeart <luc@luc.cat> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent f5b6aa7 commit b507c13

4 files changed

Lines changed: 139 additions & 1 deletion

File tree

src/lib/components/ControlModules/ClassicControlModule.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import ControlListener from './ControlListener.svelte';
1313
import ActionButtons from './impl/ActionButtons.svelte';
1414
import CircleSlider from './impl/CircleSlider.svelte';
15+
import ShockerMenu from './impl/ShockerMenu.svelte';
1516
1617
interface Props {
1718
shocker: ShockerResponse;
@@ -36,7 +37,12 @@
3637
class="border-surface-400-500-token flex flex-col items-center justify-center gap-2 overflow-hidden rounded-md border p-2"
3738
>
3839
<!-- Title -->
39-
<h2 class="w-full truncate px-4 text-center text-lg font-bold">{shocker.name}</h2>
40+
<h2 class="w-full truncate px-4 text-center text-lg font-bold flex justify-between">
41+
<span>
42+
{shocker.name}
43+
</span>
44+
<ShockerMenu {shocker} />
45+
</h2>
4046
<!-- Sliders -->
4147
<div class="flex items-center gap-2">
4248
<CircleSlider name="Intensity" bind:value={intensity} {...ControlIntensityProps} />
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script lang="ts">
2+
import { Ellipsis } from '@lucide/svelte';
3+
import { goto } from '$app/navigation';
4+
import type { ShockerResponse } from '$lib/api/internal/v1';
5+
import { Button } from '$lib/components/ui/button';
6+
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
7+
8+
interface Props {
9+
shocker: ShockerResponse;
10+
}
11+
12+
let { shocker }: Props = $props();
13+
14+
function viewLogs() {
15+
goto(`/shockers/logs/${shocker.id}`);
16+
}
17+
</script>
18+
19+
<DropdownMenu.Root>
20+
<DropdownMenu.Trigger>
21+
{#snippet child({ props })}
22+
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
23+
<span class="sr-only">Open menu</span>
24+
<Ellipsis class="size-4" />
25+
</Button>
26+
{/snippet}
27+
</DropdownMenu.Trigger>
28+
<DropdownMenu.Content>
29+
<DropdownMenu.Item class="cursor-pointer" onclick={viewLogs}>View Logs</DropdownMenu.Item>
30+
</DropdownMenu.Content>
31+
</DropdownMenu.Root>

src/lib/stores/ShockerLogStore.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { shockersV1Api } from '$lib/api';
2+
import { type LogEntry } from '$lib/api/internal/v1';
3+
import { handleApiError } from '$lib/errorhandling/apiErrorHandling';
4+
import { writable } from 'svelte/store';
5+
6+
export const ShockerLogStore = writable<Map<string, LogEntry[]>>(new Map());
7+
8+
export function fetchLogsForShocker(shockerId: string) {
9+
shockersV1Api
10+
.shockerGetShockerLogs(shockerId)
11+
.then((response) => {
12+
if (!response.data) {
13+
throw new Error(`Failed to fetch logs: ${response.message}`);
14+
}
15+
16+
const grouped = response.data.reduce((acc, entry) => {
17+
if (!acc.has(entry.id)) {
18+
acc.set(entry.id, []);
19+
}
20+
acc.get(entry.id)!.push({
21+
id: entry.id,
22+
createdOn: new Date(entry.createdOn),
23+
type: entry.type,
24+
controlledBy: entry.controlledBy,
25+
intensity: entry.intensity,
26+
duration: entry.duration,
27+
});
28+
return acc;
29+
}, new Map<string, LogEntry[]>());
30+
ShockerLogStore.set(grouped);
31+
})
32+
.catch(handleApiError);
33+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<script lang="ts">
2+
import RotateCcw from '@lucide/svelte/icons/rotate-ccw';
3+
import type { ColumnDef, SortingState } from '@tanstack/table-core';
4+
import { page } from '$app/state';
5+
import type { LogEntry } from '$lib/api/internal/v1';
6+
import Container from '$lib/components/Container.svelte';
7+
import {
8+
CreateColumnDef,
9+
CreateSortableColumnDef,
10+
LocaleDateTimeRenderer,
11+
NumberRenderer,
12+
RenderCell,
13+
} from '$lib/components/Table/ColumnUtils';
14+
import DataTable from '$lib/components/Table/DataTableTemplate.svelte';
15+
import Button from '$lib/components/ui/button/button.svelte';
16+
import * as Card from '$lib/components/ui/card';
17+
import { ShockerLogStore, fetchLogsForShocker } from '$lib/stores/ShockerLogStore';
18+
import { onMount } from 'svelte';
19+
20+
let data = $derived.by<LogEntry[]>(() => {
21+
const logMap = $ShockerLogStore;
22+
let allLogs: LogEntry[] = [];
23+
logMap.forEach((logs) => {
24+
allLogs = allLogs.concat(logs);
25+
});
26+
return allLogs;
27+
});
28+
29+
let sorting = $state<SortingState>([{ id: 'createdOn', desc: true }]);
30+
31+
const columns: ColumnDef<LogEntry>[] = [
32+
CreateSortableColumnDef('createdOn', 'Time', LocaleDateTimeRenderer),
33+
CreateSortableColumnDef('type', 'Type', (t) => RenderCell(String(t))),
34+
CreateSortableColumnDef('controlledBy', 'By', (c) => RenderCell(c.customName ?? c.name)),
35+
CreateSortableColumnDef('intensity', 'Intensity', NumberRenderer),
36+
CreateSortableColumnDef('duration', 'Duration', NumberRenderer),
37+
];
38+
39+
let shockerId = page.params.shockerId ?? '';
40+
41+
function refresh() {
42+
if (!shockerId) return;
43+
fetchLogsForShocker(shockerId);
44+
}
45+
46+
onMount(() => {
47+
if (shockerId) refresh();
48+
});
49+
</script>
50+
51+
<Container>
52+
<Card.Header class="w-full">
53+
<Card.Title class="flex items-center justify-between space-x-2 text-3xl">
54+
<div class="flex items-center space-x-2">
55+
<span>Shocker Logs</span>
56+
</div>
57+
<div class="flex items-center justify-end space-x-2">
58+
<Button variant="ghost" aria-label="Refresh logs" onclick={refresh}>
59+
<RotateCcw />
60+
</Button>
61+
</div>
62+
</Card.Title>
63+
<Card.Description>These are the logs for the specified shocker.</Card.Description>
64+
</Card.Header>
65+
<div class="w-full p-6 gap-6 grid">
66+
<DataTable {data} {columns} bind:sorting />
67+
</div>
68+
</Container>

0 commit comments

Comments
 (0)