Switched to typescript instead of javascript

This commit is contained in:
2026-03-11 00:17:15 +01:00 Verified
parent 4751f18ee6
commit 368dc41172
20 changed files with 550 additions and 263 deletions
@@ -0,0 +1,155 @@
// frontend/src/components/Bracket/BracketView.tsx
import React, { useEffect, useRef, useState } from 'react';
import { MatchData } from '../../types';
import Podium from "../Tournament/Podium";
import MatchCard from "./MatchCard";
interface BracketViewProps {
matches: MatchData[];
onMatchClick: (match: MatchData) => void;
}
export default function BracketView({ matches, onMatchClick }: BracketViewProps) {
const containerRef = useRef<HTMLDivElement>(null);
const [lines, setLines] = useState<React.ReactElement[]>([]);
useEffect(() => {
const draw = () => {
if (!containerRef.current) return;
const container = containerRef.current.getBoundingClientRect();
const newLines: React.ReactElement[] = [];
matches.forEach(m => {
if (m.winner_next_match_id) {
const sEl = document.getElementById(`match-${m.id}`);
const eEl = document.getElementById(`match-${m.winner_next_match_id}`);
if (sEl && eEl) {
const r1 = sEl.getBoundingClientRect(), r2 = eEl.getBoundingClientRect();
const sx = r1.right - container.left, sy = r1.top + r1.height / 2 - container.top;
const ex = r2.left - container.left, ey = r2.top + r2.height / 2 - container.top;
const c1 = sx + (ex - sx) / 2;
newLines.push(
<path key={`w-${m.id}`} d={`M ${sx} ${sy} C ${c1} ${sy}, ${c1} ${ey}, ${ex} ${ey}`} className="stroke-zinc-400/70 dark:stroke-zinc-700/70 print:stroke-zinc-400! fill-none stroke-[2px]" />
);
}
}
if (m.loser_next_match_id) {
const targetMatch = matches.find(x => x.id === m.loser_next_match_id);
if (targetMatch && targetMatch.bracket === 'Finals') {
const sEl = document.getElementById(`match-${m.id}`);
const eEl = document.getElementById(`match-${targetMatch.id}`);
if (sEl && eEl) {
const r1 = sEl.getBoundingClientRect(), r2 = eEl.getBoundingClientRect();
const sx = r1.right - container.left, sy = r1.top + r1.height / 2 - container.top;
const ex = r2.left - container.left, ey = r2.top + r2.height / 2 - container.top;
const c1 = sx + (ex - sx) / 2;
newLines.push(
<path key={`l-${m.id}`} strokeDasharray="6 6" d={`M ${sx} ${sy} C ${c1} ${sy}, ${c1} ${ey}, ${ex} ${ey}`} className="stroke-zinc-300 dark:stroke-zinc-700 print:stroke-zinc-400! fill-none stroke-[1.5px]" />
);
}
}
}
});
setLines(newLines);
};
const t = setTimeout(draw, 100);
window.addEventListener('resize', draw);
const handlePrint = () => { draw(); setTimeout(draw, 100); };
const mql = window.matchMedia('print');
mql.addEventListener('change', handlePrint);
window.addEventListener('beforeprint', handlePrint);
window.addEventListener('afterprint', draw);
return () => {
clearTimeout(t);
window.removeEventListener('resize', draw);
mql.removeEventListener('change', handlePrint);
window.removeEventListener('beforeprint', handlePrint);
window.removeEventListener('afterprint', draw);
};
}, [matches]);
const renderRound = (list: MatchData[]) => {
const rounds: Record<number, MatchData[]> = {};
list.forEach(m => (rounds[m.round] = rounds[m.round] || []).push(m));
const roundKeys = Object.keys(rounds).map(Number).sort((a, b) => a - b);
return roundKeys.map((r) => {
let matchesInRound = rounds[r];
matchesInRound.sort((a, b) => a.number - b.number);
return (
<div key={r} className="flex flex-col gap-10 z-10 w-64 shrink-0 justify-around">
{matchesInRound.map(m => <MatchCard key={m.id} match={m} onClick={onMatchClick} />)}
</div>
);
});
};
const isDoubleElim = matches.some(m => m.bracket === 'Loser');
const wb = matches.filter(m => m.bracket === 'Winner');
const lb = matches.filter(m => m.bracket === 'Loser');
const finals = matches.filter(m => m.bracket === 'Finals');
let displayWb = [...wb];
let displayFinals = [...finals];
if (!isDoubleElim && displayWb.length > 0) {
const maxRound = Math.max(...displayWb.map(m => m.round));
const gfIndex = displayWb.findIndex(m => m.round === maxRound);
if (gfIndex !== -1) {
const gfMatch = displayWb.splice(gfIndex, 1)[0];
displayFinals.unshift(gfMatch);
}
}
return (
<div className="transition-colors w-full h-full overflow-auto print:overflow-visible print:h-auto print:w-auto bg-zinc-50 dark:bg-zinc-950 bg-[radial-gradient(var(--color-zinc-300)_1px,transparent_1px)] dark:bg-[radial-gradient(var(--color-zinc-800)_1px,transparent_1px)] bg-size-[20px_20px] print:bg-white! print:bg-none!">
<style>
{`@media print {
@page { size: landscape; margin: 0.5cm; }
body { -webkit-print-color-adjust: exact; print-color-adjust: exact; background: white !important; }
}`}
</style>
<div ref={containerRef} className="relative min-w-max min-h-full p-12 flex gap-20 items-center">
<svg className="absolute inset-0 w-full h-full pointer-events-none z-0 print:overflow-visible">
{lines}
</svg>
<div className="flex flex-col gap-24">
<div className="relative">
<div className="absolute -top-8 left-0 text-[10px] font-black uppercase tracking-[0.2em] text-zinc-400 print:text-black!">Winners Bracket</div>
<div className="flex gap-20">{renderRound(displayWb)}</div>
</div>
{isDoubleElim && (
<div className="relative pt-8 border-t border-dashed border-zinc-300 dark:border-zinc-800 print:border-zinc-400!">
<div className="absolute top-4 left-0 text-[10px] font-black uppercase tracking-[0.2em] text-zinc-400 print:text-black!">Losers Bracket</div>
<div className="flex gap-20 mt-4">{renderRound(lb)}</div>
</div>
)}
</div>
<div className="flex flex-col justify-center gap-6 z-10">
{displayFinals.length > 0 && (
<div className="relative flex flex-col gap-6">
<div className="absolute -top-10 left-1/2 -translate-x-1/2 text-[10px] font-black uppercase bg-orange-100 dark:bg-orange-900/30 text-orange-600 print:bg-transparent! print:border-black! print:text-black! px-4 py-1.5 rounded-full border border-orange-200 dark:border-orange-800 shadow-sm whitespace-nowrap">
Championship
</div>
{displayFinals.map(m => <MatchCard key={m.id} match={m} onClick={onMatchClick} />)}
</div>
)}
</div>
<div className="flex flex-col justify-center gap-6 z-10">
{/* @ts-ignore - Assuming Podium was typed earlier or needs its own MatchData import */}
<Podium matches={matches} />
</div>
</div>
</div>
);
}