Typscript final
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// frontend/src/pages/Dashboard.tsx
|
||||
|
||||
import { Calendar, ChevronDown, ChevronUp, History, Plus, PlusCircle, SlidersHorizontal } from 'lucide-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useNavigate, useOutletContext } from 'react-router-dom';
|
||||
import DashCard from '../components/Dashboard/DashCard';
|
||||
import TournamentForm from '../components/Forms/TournamentForm';
|
||||
@@ -11,8 +11,11 @@ import api, { WS_URL } from '../services/api';
|
||||
|
||||
export interface TournamentType {
|
||||
id: string | number;
|
||||
name: string;
|
||||
timestamp: string;
|
||||
[key: string]: any;
|
||||
type: string;
|
||||
team_count: number;
|
||||
court_count: number;
|
||||
}
|
||||
|
||||
interface OutletContextType {
|
||||
@@ -32,20 +35,22 @@ export default function Dashboard() {
|
||||
const [showAllFuture, setShowAllFuture] = useState<boolean>(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const loadDashboard = async () => {
|
||||
const loadDashboard = useCallback(async () => {
|
||||
try {
|
||||
const res: any = await api.get('/tournaments');
|
||||
const list = Array.isArray(res) ? res : (res.items || []);
|
||||
setTournaments(list);
|
||||
const res = await api.get<{ items?: TournamentType[] } | TournamentType[]>('/tournaments');
|
||||
const list = Array.isArray(res) ? res : (res?.items || []);
|
||||
setTournaments(list as TournamentType[]);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setNavTitle('Dashboard');
|
||||
setNavSubtitle('');
|
||||
loadDashboard();
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
void loadDashboard();
|
||||
|
||||
localStorage.removeItem('volley_view');
|
||||
|
||||
let ws: WebSocket;
|
||||
@@ -63,7 +68,7 @@ export default function Dashboard() {
|
||||
connect();
|
||||
|
||||
return () => { if (ws) ws.close(); };
|
||||
}, [setNavTitle, setNavSubtitle]);
|
||||
}, [setNavTitle, setNavSubtitle, loadDashboard]);
|
||||
|
||||
const handleEdit = (t: TournamentType) => {
|
||||
setEditTarget(t);
|
||||
@@ -126,7 +131,7 @@ export default function Dashboard() {
|
||||
</span> Live Events
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{groups.live.map(t => <DashCard key={t.id} t={t} isAdmin={isAdmin} onSelect={(id: string) => navigate(`/tournaments/${id}`)} onEdit={handleEdit} />)}
|
||||
{groups.live.map(t => <DashCard key={t.id} t={t} isAdmin={isAdmin} onSelect={(id) => navigate(`/tournaments/${id}`)} onEdit={handleEdit} />)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
@@ -138,7 +143,7 @@ export default function Dashboard() {
|
||||
{groups.future.length > 0 ? (
|
||||
<>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{futureShow.map(t => <DashCard key={t.id} t={t} isAdmin={isAdmin} onSelect={(id: string) => navigate(`/tournaments/${id}`)} onEdit={handleEdit} />)}
|
||||
{futureShow.map(t => <DashCard key={t.id} t={t} isAdmin={isAdmin} onSelect={(id) => navigate(`/tournaments/${id}`)} onEdit={handleEdit} />)}
|
||||
</div>
|
||||
{groups.future.length > 4 && (
|
||||
<div className="mt-8 text-center">
|
||||
@@ -160,7 +165,7 @@ export default function Dashboard() {
|
||||
</button>
|
||||
{showPast && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 opacity-75 hover:opacity-100 transition-opacity">
|
||||
{groups.past.map(t => <DashCard key={t.id} t={t} isAdmin={isAdmin} onSelect={(id: string) => navigate(`/tournaments/${id}`)} onEdit={handleEdit} />)}
|
||||
{groups.past.map(t => <DashCard key={t.id} t={t} isAdmin={isAdmin} onSelect={(id) => navigate(`/tournaments/${id}`)} onEdit={handleEdit} />)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function Login() {
|
||||
const res = await api.postForm<TokenResponse>('/auth/token', formData);
|
||||
localStorage.setItem('volleyToken', res.access_token);
|
||||
navigate('/');
|
||||
} catch (err) {
|
||||
} catch {
|
||||
setError('Invalid credentials.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// frontend/src/pages/Tournament.tsx
|
||||
|
||||
import { type SetData } from '../types';
|
||||
import { CalendarDays, Loader2, Network, SlidersHorizontal } from 'lucide-react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useOutletContext, useParams } from 'react-router-dom';
|
||||
@@ -134,9 +135,9 @@ export default function Tournament() {
|
||||
});
|
||||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
const fetchData = React.useCallback(async () => {
|
||||
try {
|
||||
const res: any = await api.get(`/tournaments/${id}`);
|
||||
const res = await api.get<{ name: string, timestamp: string, matches: RawMatch[], courts: Court[], teams: Team[] }>(`/tournaments/${id}`);
|
||||
setNavTitle(res.name);
|
||||
setNavSubtitle(new Date(res.timestamp).toLocaleDateString());
|
||||
setMatches(processMatches(res.matches, res.courts, res.teams));
|
||||
@@ -145,10 +146,10 @@ export default function Tournament() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [id, setNavTitle, setNavSubtitle]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
void fetchData();
|
||||
if (wsRef.current) return;
|
||||
|
||||
const connect = () => {
|
||||
@@ -156,7 +157,7 @@ export default function Tournament() {
|
||||
wsRef.current = ws;
|
||||
ws.onmessage = (e: MessageEvent) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
if (msg.type === 'tournament_update' && msg.id === id) fetchData();
|
||||
if (msg.type === 'tournament_update' && msg.id === id) void fetchData();
|
||||
};
|
||||
ws.onclose = () => { wsRef.current = null; };
|
||||
};
|
||||
@@ -167,7 +168,7 @@ export default function Tournament() {
|
||||
if (wsRef.current?.readyState === 1) wsRef.current.close();
|
||||
wsRef.current = null;
|
||||
};
|
||||
}, [id, setNavTitle, setNavSubtitle]);
|
||||
}, [id, fetchData]);
|
||||
|
||||
const handleDeleteTournament = async (tId: string | number) => {
|
||||
if (window.confirm("Purge this tournament?")) {
|
||||
@@ -194,8 +195,8 @@ export default function Tournament() {
|
||||
|
||||
<div className="flex-1 overflow-hidden relative print:overflow-visible print:h-auto print:block">
|
||||
{view === 'bracket'
|
||||
? <BracketView matches={matches} onMatchClick={setScoreMatch} />
|
||||
: <ScheduleView schedule={matches} onMatchClick={setScoreMatch} />
|
||||
? <BracketView matches={matches} onMatchClick={(m) => setScoreMatch(m as ProcessedMatch)} />
|
||||
: <ScheduleView schedule={matches} onMatchClick={(m) => setScoreMatch(m as ProcessedMatch)} />
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -209,9 +210,8 @@ export default function Tournament() {
|
||||
await api.delete(`/tournaments/${id}/matches/${mid}/score?code=${encodeURIComponent(c || '')}`);
|
||||
setScoreMatch(null);
|
||||
}}
|
||||
onSubmit={async (mid: string | number, s: any, c: string) => {
|
||||
onSubmit={async (mid: string | number, s: SetData[], c: string) => {
|
||||
const method = scoreMatch.isFinished ? 'patch' : 'post';
|
||||
// @ts-ignore - Indexing api dynamically
|
||||
await api[method](`/tournaments/${id}/matches/${mid}/score`, { sets: s, code: c });
|
||||
setScoreMatch(null);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user