Added seperate courts
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// frontend/src/components/Dashboard/DashCard.tsx
|
||||
|
||||
import { MapPin, SlidersHorizontal, Users } from 'lucide-react';
|
||||
import { SlidersHorizontal, Users } from 'lucide-react';
|
||||
import CourtIcon from '../../assets/court.svg?react';
|
||||
|
||||
interface TournamentSummary {
|
||||
id: string | number;
|
||||
@@ -45,7 +46,7 @@ export default function DashCard({ t, isAdmin, onSelect, onEdit }: DashCardProps
|
||||
{t.team_count} Teams
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 font-medium">
|
||||
<MapPin size={16} className="text-orange-500" />
|
||||
<CourtIcon width={16} height={16} className="text-orange-500" />
|
||||
{t.court_count} Courts
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
// frontend/src/components/Forms/TournamentForm.tsx
|
||||
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { Loader2, Plus } from 'lucide-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import api from '../../services/api';
|
||||
|
||||
interface Court {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface TournamentSettings {
|
||||
id: string | number;
|
||||
name: string;
|
||||
@@ -11,7 +16,7 @@ interface TournamentSettings {
|
||||
type: string;
|
||||
timestamp: string;
|
||||
duration: number;
|
||||
courts: { name?: string }[];
|
||||
courts: Court[];
|
||||
teams: { name?: string }[];
|
||||
}
|
||||
|
||||
@@ -23,33 +28,55 @@ interface TournamentFormProps {
|
||||
|
||||
export default function TournamentForm({ tournamentId, onSuccess, onDelete }: TournamentFormProps) {
|
||||
const [initialData, setInitialData] = useState<TournamentSettings | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(!!tournamentId);
|
||||
const [globalCourts, setGlobalCourts] = useState<Court[]>([]);
|
||||
const [selectedCourts, setSelectedCourts] = useState<number[]>([]);
|
||||
const [newCourtName, setNewCourtName] = useState('');
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!tournamentId) {
|
||||
setInitialData(null);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchSettings = async () => {
|
||||
const fetchDependencies = async () => {
|
||||
try {
|
||||
const data = await api.get<TournamentSettings>(`/tournaments/${tournamentId}/settings`);
|
||||
setInitialData(data);
|
||||
const courtsRes = await api.get<Court[]>('/courts');
|
||||
setGlobalCourts(courtsRes);
|
||||
|
||||
if (tournamentId) {
|
||||
const data = await api.get<TournamentSettings>(`/tournaments/${tournamentId}/settings`);
|
||||
setInitialData(data);
|
||||
setSelectedCourts(data.courts.map(c => c.id));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError("Failed to load tournament settings.");
|
||||
setError("Failed to load tournament data.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSettings();
|
||||
void fetchDependencies();
|
||||
}, [tournamentId]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
const handleAddCourt = async () => {
|
||||
if (!newCourtName.trim()) return;
|
||||
try {
|
||||
const added = await api.post<Court>('/courts', { name: newCourtName.trim() });
|
||||
setGlobalCourts([...globalCourts, added]);
|
||||
setSelectedCourts([...selectedCourts, added.id]);
|
||||
setNewCourtName('');
|
||||
} catch {
|
||||
setError("Failed to create new court.");
|
||||
}
|
||||
};
|
||||
|
||||
const toggleCourt = (id: number) => {
|
||||
setSelectedCourts(prev =>
|
||||
prev.includes(id) ? prev.filter(c => c !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
setError(null);
|
||||
@@ -57,7 +84,6 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }: To
|
||||
const formData = new FormData(e.currentTarget);
|
||||
|
||||
const rawTeams = formData.get('teams') as string;
|
||||
const rawCourts = formData.get('courts') as string;
|
||||
const date = formData.get('date') as string;
|
||||
const startTime = formData.get('start_time') as string;
|
||||
const typeRaw = formData.get('type') as string;
|
||||
@@ -66,7 +92,6 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }: To
|
||||
const code = formData.get('code') as string;
|
||||
|
||||
const teams = rawTeams.split('\n').map(t => t.trim()).filter(t => t.length > 0);
|
||||
const courts = rawCourts.split(',').map(c => c.trim()).filter(c => c.length > 0);
|
||||
|
||||
if (teams.length < 2) {
|
||||
setError("At least 2 teams required.");
|
||||
@@ -74,6 +99,12 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }: To
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedCourts.length === 0) {
|
||||
setError("Please select at least one court.");
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const timestamp = `${date}T${startTime}:00`;
|
||||
const formattedType = typeRaw.charAt(0).toUpperCase() + typeRaw.slice(1);
|
||||
const parsedDuration = parseInt(duration, 10);
|
||||
@@ -81,39 +112,25 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }: To
|
||||
try {
|
||||
if (tournamentId) {
|
||||
const basePayload = {
|
||||
name,
|
||||
code,
|
||||
type: formattedType,
|
||||
timestamp,
|
||||
duration: parsedDuration
|
||||
name, code, type: formattedType, timestamp, duration: parsedDuration
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
api.patch(`/tournaments/${tournamentId}`, basePayload),
|
||||
api.patch(`/tournaments/${tournamentId}/teams`, teams),
|
||||
api.patch(`/tournaments/${tournamentId}/courts`, courts)
|
||||
api.patch(`/tournaments/${tournamentId}/courts`, selectedCourts)
|
||||
]);
|
||||
} else {
|
||||
const fullPayload = {
|
||||
name,
|
||||
code,
|
||||
type: formattedType,
|
||||
timestamp,
|
||||
duration: parsedDuration,
|
||||
teams,
|
||||
courts
|
||||
name, code, type: formattedType, timestamp, duration: parsedDuration,
|
||||
teams, courts: selectedCourts
|
||||
};
|
||||
await api.post('/tournaments', fullPayload);
|
||||
}
|
||||
onSuccess();
|
||||
} catch (err: unknown) {
|
||||
console.error(err);
|
||||
const error = err as { detail?: string | Array<{ loc: string[]; msg: string }> };
|
||||
if (Array.isArray(error.detail)) {
|
||||
setError(error.detail.map((e) => `${e.loc.join('.')}: ${e.msg}`).join(', '));
|
||||
} else {
|
||||
setError(typeof error.detail === 'string' ? error.detail : "Error saving tournament");
|
||||
}
|
||||
setError("Error saving tournament");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@@ -147,35 +164,17 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }: To
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-xs font-bold text-zinc-500 uppercase">Name</label>
|
||||
<input
|
||||
name="name"
|
||||
defaultValue={initialData?.name}
|
||||
required
|
||||
placeholder="My Awesome Tournament"
|
||||
autoFocus
|
||||
className="w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-600 rounded p-2 focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white"
|
||||
/>
|
||||
<input name="name" defaultValue={initialData?.name} required placeholder="My Awesome Tournament" autoFocus className="w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-600 rounded p-2 focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="font-bold text-zinc-500 text-xs uppercase">Code</label>
|
||||
<input
|
||||
name="code"
|
||||
defaultValue={initialData?.code}
|
||||
required
|
||||
placeholder="••••"
|
||||
autoComplete="off"
|
||||
className="w-full h-10 bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-600 rounded p-2 text-center font-mono focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white"
|
||||
/>
|
||||
<input name="code" defaultValue={initialData?.code} required placeholder="••••" autoComplete="off" className="w-full h-10 bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-600 rounded p-2 text-center font-mono focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="font-bold text-zinc-500 text-xs uppercase">Type</label>
|
||||
<select
|
||||
name="type"
|
||||
defaultValue={initialData?.type?.toLowerCase() || "double"}
|
||||
className="w-full h-10 bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-600 rounded p-2 focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white"
|
||||
>
|
||||
<select name="type" defaultValue={initialData?.type?.toLowerCase() || "double"} className="w-full h-10 bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-600 rounded p-2 focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white">
|
||||
<option value="double">Double Elimination</option>
|
||||
<option value="single">Single Elimination</option>
|
||||
</select>
|
||||
@@ -185,43 +184,50 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }: To
|
||||
<div className="grid grid-cols-7 gap-4">
|
||||
<div className="col-span-2">
|
||||
<label className="text-xs font-bold text-zinc-500 uppercase mb-1 block">Duration</label>
|
||||
<input
|
||||
type="number"
|
||||
name="duration"
|
||||
defaultValue={initialData?.duration || 30}
|
||||
min="0"
|
||||
className="w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-600 rounded p-2 h-10 text-base appearance-none focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white"
|
||||
/>
|
||||
<input type="number" name="duration" defaultValue={initialData?.duration || 30} min="0" className="w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-600 rounded p-2 h-10 text-base appearance-none focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white" />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="text-xs font-bold text-zinc-500 uppercase mb-1 block">Start Time</label>
|
||||
<input
|
||||
type="time"
|
||||
name="start_time"
|
||||
defaultValue={defaultTime}
|
||||
className="w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-600 rounded p-2 h-10 text-base appearance-none focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white"
|
||||
/>
|
||||
<input type="time" name="start_time" defaultValue={defaultTime} className="w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-600 rounded p-2 h-10 text-base appearance-none focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white" />
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<label className="text-xs font-bold text-zinc-500 uppercase mb-1 block">Date</label>
|
||||
<input
|
||||
type="date"
|
||||
name="date"
|
||||
defaultValue={defaultDate}
|
||||
className="w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded p-2 h-10 text-base appearance-none focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white"
|
||||
/>
|
||||
<input type="date" name="date" defaultValue={defaultDate} className="w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded p-2 h-10 text-base appearance-none focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-bold text-zinc-500 uppercase">Courts</label>
|
||||
<input
|
||||
name="courts"
|
||||
placeholder="Center Court, Court 1"
|
||||
defaultValue={initialData?.courts?.map(c => c.name || c).join(', ')}
|
||||
required
|
||||
className="w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-600 rounded p-2 focus:border-orange-500 outline-none transition text-zinc-900 dark:text-white"
|
||||
/>
|
||||
{/* --- NEW: GLOBAL COURT SELECTOR --- */}
|
||||
<div className="p-3 bg-zinc-50 dark:bg-zinc-900/50 rounded-lg border border-zinc-200 dark:border-zinc-800">
|
||||
<label className="text-xs font-bold text-zinc-500 uppercase mb-2 block">Venue Courts</label>
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{globalCourts.map(c => (
|
||||
<button
|
||||
key={c.id}
|
||||
type="button"
|
||||
onClick={() => toggleCourt(c.id)}
|
||||
className={`px-3 py-1.5 rounded-full text-xs font-bold uppercase tracking-wider transition ${selectedCourts.includes(c.id)
|
||||
? 'bg-orange-500 text-white shadow-md'
|
||||
: 'bg-zinc-200 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 hover:bg-zinc-300 dark:hover:bg-zinc-700'
|
||||
}`}
|
||||
>
|
||||
{c.name}
|
||||
</button>
|
||||
))}
|
||||
{globalCourts.length === 0 && <span className="text-xs italic text-zinc-400">No courts registered yet.</span>}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={newCourtName}
|
||||
onChange={(e) => setNewCourtName(e.target.value)}
|
||||
placeholder="Add new court..."
|
||||
className="flex-1 bg-white dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-700 rounded px-3 py-1 text-sm focus:border-orange-500 outline-none text-zinc-900 dark:text-white"
|
||||
/>
|
||||
<button type="button" onClick={handleAddCourt} className="bg-zinc-200 dark:bg-zinc-800 hover:bg-zinc-300 dark:hover:bg-zinc-700 text-zinc-700 dark:text-zinc-300 px-3 py-1 rounded transition flex items-center">
|
||||
<Plus size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -239,21 +245,13 @@ export default function TournamentForm({ tournamentId, onSuccess, onDelete }: To
|
||||
|
||||
<div className="flex justify-between mt-4 pt-4 border-t border-zinc-200 dark:border-zinc-800 flex-wrap gap-y-4">
|
||||
{tournamentId && onDelete && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDelete(tournamentId)}
|
||||
className="text-red-500 text-sm hover:underline h-5 self-end"
|
||||
>
|
||||
<button type="button" onClick={() => onDelete(tournamentId)} className="text-red-500 text-sm hover:underline h-5 self-end">
|
||||
Delete Tournament
|
||||
</button>
|
||||
)}
|
||||
{!tournamentId && <div className="hidden"></div>}
|
||||
|
||||
<button
|
||||
disabled={isSubmitting}
|
||||
type="submit"
|
||||
className="bg-orange-600 hover:bg-orange-500 text-white px-6 py-2 rounded font-bold shadow-lg shadow-orange-900/20 ml-auto transition active:scale-95 disabled:opacity-50"
|
||||
>
|
||||
<button disabled={isSubmitting} type="submit" className="bg-orange-600 hover:bg-orange-500 text-white px-6 py-2 rounded font-bold shadow-lg shadow-orange-900/20 ml-auto transition active:scale-95 disabled:opacity-50">
|
||||
{isSubmitting ? 'Saving...' : (tournamentId ? 'Save Changes' : 'Create')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// frontend/src/components/Layout/Navbar.tsx
|
||||
|
||||
import { Lock, LogOut, Volleyball } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import CourtIcon from '../../assets/court.svg?react';
|
||||
|
||||
interface NavbarProps {
|
||||
title: string;
|
||||
@@ -11,39 +12,56 @@ interface NavbarProps {
|
||||
}
|
||||
|
||||
export default function Navbar({ title, subtitle, isAuthenticated, onLogout }: NavbarProps) {
|
||||
return (
|
||||
<nav className="transition-colors bg-zinc-50 dark:bg-zinc-900 border-b border-zinc-300 dark:border-zinc-800 sticky top-0 z-100 px-3 sm:px-6 py-3 sm:py-4 flex justify-between items-center shadow-md shrink-0">
|
||||
<Link to="/" className="flex items-center gap-2 sm:gap-4 cursor-pointer group select-none shrink-0">
|
||||
<div className="p-1.5 sm:p-2.5 bg-orange-600 rounded-xl group-hover:rotate-12 transition-transform shadow-lg shadow-orange-600/30 active:scale-90">
|
||||
<Volleyball className="text-white" size={20} />
|
||||
</div>
|
||||
<div className="hidden sm:block">
|
||||
<h1 className="text-2xl font-black tracking-tighter leading-none text-zinc-900 dark:text-white">VolleyManager</h1>
|
||||
<p className="text-[9px] font-black text-zinc-500 dark:text-zinc-400 uppercase tracking-widest mt-0.5">Tournament Ops</p>
|
||||
</div>
|
||||
</Link>
|
||||
const location = useLocation();
|
||||
|
||||
<div className="transition-colors absolute left-1/2 -translate-x-1/2 text-center pointer-events-none w-full max-w-35 xs:max-w-[180px] sm:max-w-100">
|
||||
<div className="font-black uppercase text-[10px] sm:text-sm tracking-widest sm:tracking-[0.3em] text-zinc-900 dark:text-white truncate leading-none mb-1">
|
||||
return (
|
||||
<nav className="bg-white dark:bg-zinc-900 border-b border-zinc-200 dark:border-zinc-800 px-4 py-3 flex items-center justify-between shadow-sm sticky top-0 z-50">
|
||||
|
||||
{/* Left: Logo & Main Navigation */}
|
||||
<div className="flex items-center gap-6">
|
||||
<Link to="/" className="flex items-center gap-2 shrink-0">
|
||||
<div className="p-2 bg-orange-600 rounded-xl shadow-lg shadow-orange-600/20">
|
||||
<Volleyball className="text-white" size={20} />
|
||||
</div>
|
||||
<div className="hidden lg:block">
|
||||
<h1 className="text-lg font-black text-zinc-900 dark:text-white leading-none">VolleyManager</h1>
|
||||
</div>
|
||||
</Link>
|
||||
<Link to="/courts" className="md:hidden p-2 text-zinc-600 dark:text-zinc-400 bg-zinc-100 hover:bg-zinc-300 dark:hover:bg-zinc-800 rounded-lg">
|
||||
<CourtIcon width={20} height={20} />
|
||||
</Link>
|
||||
|
||||
<div className="hidden md:flex items-center gap-1 bg-zinc-100 dark:bg-zinc-800 p-1 rounded-lg">
|
||||
<Link to="/" className={`px-4 py-1.5 rounded-md text-xs font-black uppercase tracking-wider transition ${location.pathname === '/' ? 'bg-white dark:bg-zinc-700 shadow-sm text-orange-600' : 'text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-200'}`}>
|
||||
Tournaments
|
||||
</Link>
|
||||
<Link to="/courts" className={`flex items-center gap-2 px-4 py-1.5 rounded-md text-xs font-black uppercase tracking-wider transition ${location.pathname === '/courts' ? 'bg-orange-600 text-white shadow-md' : 'text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-200'}`}>
|
||||
Courts
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Center: Title (Responsive truncation) */}
|
||||
<div className="absolute left-1/2 -translate-x-1/2 text-center hidden sm:block pointer-events-none">
|
||||
<div className="font-black uppercase text-xs tracking-[0.2em] text-zinc-900 dark:text-white truncate max-w-37.5 md:max-w-62.5">
|
||||
{title || 'Dashboard'}
|
||||
</div>
|
||||
{subtitle && (
|
||||
<div className="text-[8px] sm:text-[10px] font-black text-zinc-400 dark:text-zinc-500 uppercase tracking-widest leading-none">
|
||||
<div className="text-[9px] font-bold text-zinc-400 uppercase tracking-widest mt-0.5">
|
||||
{subtitle}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 sm:gap-4 items-center shrink-0">
|
||||
{/* Right: Actions */}
|
||||
<div className="flex items-center gap-3">
|
||||
{isAuthenticated ? (
|
||||
<>
|
||||
<button onClick={onLogout} title="Sign Out" className="text-zinc-400 hover:text-red-500 transition active:scale-90 shrink-0">
|
||||
<LogOut size={18} className="sm:size-5.5" />
|
||||
</button>
|
||||
</>
|
||||
<button onClick={onLogout} className="p-2 text-zinc-400 hover:text-red-500 transition rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800">
|
||||
<LogOut size={20} />
|
||||
</button>
|
||||
) : (
|
||||
<Link to="/login" className="text-orange-600 font-black flex items-center gap-1.5 text-[9px] sm:text-[10px] uppercase tracking-widest hover:text-orange-500 transition group p-1.5 sm:p-2 rounded-xl hover:bg-orange-50 dark:hover:bg-orange-950/20">
|
||||
<Lock size={12} className="sm:size-3.5 group-hover:-translate-y-0.5 transition-transform" /> <span className="hidden xs:inline">Login</span>
|
||||
<Link to="/login" className="flex items-center gap-2 px-4 py-2 bg-orange-50 dark:bg-orange-900/20 text-orange-600 dark:text-orange-400 rounded-lg text-xs font-black uppercase tracking-wider hover:bg-orange-100 transition">
|
||||
<Lock size={14} /> Login
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// frontend/src/components/Tournament/ScoreModal.tsx
|
||||
|
||||
import { type SetData } from '../../types';
|
||||
import { Clock, Eraser, MapPin, Trophy } from 'lucide-react';
|
||||
import { Clock, Eraser, Trophy } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import Modal from '../UI/Modal';
|
||||
import WhistleIcon from "../../assets/whistle.svg?react"
|
||||
import CourtIcon from '../../assets/court.svg?react';
|
||||
|
||||
interface MatchData {
|
||||
id: string | number;
|
||||
@@ -64,7 +65,7 @@ const ScoreForm = ({ match, isAuthenticated, onSubmit, onClear }: ScoreFormProps
|
||||
</div>
|
||||
<div className="h-4 w-px bg-zinc-300 dark:bg-zinc-800" />
|
||||
<div className="flex items-center gap-2 text-sm font-mono text-zinc-900 dark:text-white">
|
||||
<MapPin className="text-orange-500" size={16} />
|
||||
<CourtIcon className="text-orange-500" width={16} height={16} />
|
||||
<span>{match.court || "TBD"}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user