Files
Website/src/components/event/EleminationDisplay.svelte
Chaoscaot f13305d116
Some checks failed
SteamWarCI Build failed
feat: Add Team info Sidebar
2025-12-20 18:36:33 +01:00

143 lines
4.3 KiB
Svelte

<script lang="ts">
import type {
ExtendedEvent,
EventFight,
ResponseGroups,
ResponseRelation,
} from "@type/event.ts";
import type { EleminationViewConfig } from "./types";
import EventCard from "./EventCard.svelte";
import EventFightChip from "./EventFightChip.svelte";
import { onMount, onDestroy, tick } from "svelte";
import { FightConnector, fightConnector } from "./connections.svelte.ts";
const {
event,
config,
}: { event: ExtendedEvent; config: EleminationViewConfig } = $props();
const defaultGroup: ResponseGroups = {
id: -1,
name: "Elimination",
pointsPerWin: 0,
pointsPerLoss: 0,
pointsPerDraw: 0,
type: "ELIMINATION_STAGE",
points: null,
};
function buildStages(
ev: ExtendedEvent,
finalFightId: number,
): EventFight[][] {
const fightMap = new Map<number, EventFight>(
ev.fights.map((f) => [f.id, f]),
);
const relationsByFight = new Map<number, ResponseRelation[]>();
for (const rel of ev.relations) {
const list = relationsByFight.get(rel.fight) ?? [];
list.push(rel);
relationsByFight.set(rel.fight, list);
}
const finalFight = fightMap.get(finalFightId);
if (!finalFight) return [];
const stages: EventFight[][] = [];
let currentLayer: EventFight[] = [finalFight];
const visited = new Set<number>([finalFight.id]);
while (currentLayer.length) {
stages.push(currentLayer);
const nextLayer: EventFight[] = [];
for (const fight of currentLayer) {
const rels = relationsByFight.get(fight.id) ?? [];
for (const rel of rels) {
if (rel.type === "FIGHT" && rel.fromFight) {
const src =
fightMap.get(rel.fromFight.id) ?? rel.fromFight;
if (src && !visited.has(src.id)) {
visited.add(src.id);
nextLayer.push(src);
}
}
}
}
currentLayer = nextLayer;
}
stages.reverse();
return stages;
}
function stageName(index: number, fights: EventFight[]): string {
const count = fights.length;
switch (count) {
case 1:
return `Finale`;
case 2:
return "Halbfinale";
case 4:
return "Viertelfinale";
case 8:
return "Achtelfinale";
case 16:
return "Sechzehntelfinale";
default:
return `Runde ${index + 1}`;
}
}
const stages = $derived(buildStages(event, config.finalFight));
const connector = $fightConnector;
onDestroy(() => {
connector.clearAllConnections();
});
function buildConnections() {
if (!connector) return;
connector.clearConnections();
for (const rel of event.relations) {
if (rel.type !== "FIGHT" || !rel.fromFight) continue;
const fromEl = document.getElementById(
`fight-${rel.fromFight.id}`,
) as HTMLElement | null;
const toEl = document.getElementById(
`fight-${rel.fight}-team-${rel.team.toLowerCase()}`,
) as HTMLElement | null;
if (fromEl && toEl) {
connector.addConnection(fromEl, toEl, "#9ca3af");
}
}
}
onMount(async () => {
await tick();
buildConnections();
});
</script>
{#if stages.length === 0}
<p class="text-gray-400 italic">Keine Eliminationsdaten gefunden.</p>
{:else}
<div class="flex gap-12">
{#each stages as stage, index}
<div class="flex flex-col justify-center">
<EventCard title={stageName(index, stage)}>
{#each stage as fight}
<EventFightChip
{event}
{fight}
group={fight.group ?? defaultGroup}
/>
{/each}
</EventCard>
</div>
{/each}
</div>
{/if}