Add create and show page for competition

This commit is contained in:
unurled 2025-07-03 20:18:53 +02:00
parent 1485f3daf5
commit 744308d0cb
10 changed files with 151 additions and 72 deletions

View file

@ -39,14 +39,8 @@ class TournamentController extends Controller
'description' => 'nullable|string',
'location' => 'nullable|string|max:255',
'start_date' => 'required|date',
'end_date' => 'required|date|after_or_equal:start_date',
'registration_deadline' => 'nullable|date|before_or_equal:start_date',
'max_teams' => 'nullable|integer|min:2',
'status' => 'required|in:draft,public,private',
'team_ids' => 'nullable|array',
'team_ids.*' => 'exists:teams,id',
'field_ids' => 'nullable|array',
'field_ids.*' => 'exists:fields,id',
]);
if ($validator->fails()) {
@ -59,23 +53,11 @@ class TournamentController extends Controller
$tournament->description = $request->description;
$tournament->location = $request->location;
$tournament->start_date = $request->start_date;
$tournament->end_date = $request->end_date;
$tournament->registration_deadline = $request->registration_deadline;
$tournament->max_teams = $request->max_teams;
$tournament->status = $request->status;
$tournament->owner = Auth::id();
$tournament->save();
// Associate teams with the tournament
if ($request->has('team_ids') && is_array($request->team_ids)) {
$tournament->teams()->attach($request->team_ids, ['status' => 'confirmed']);
}
// Associate fields with the tournament
if ($request->has('field_ids') && is_array($request->field_ids)) {
$tournament->fields()->attach($request->field_ids);
}
// Create a default scheduling mode
$schedulingMode = new SchedulingMode();
$schedulingMode->competition_id = $tournament->id;

View file

@ -20,7 +20,6 @@ class Competition extends Model
'name',
'description',
'start_date',
'end_date',
'status',
'location',
'max_teams',

View file

@ -4,7 +4,9 @@ namespace App\Providers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@ -65,5 +67,15 @@ class AppServiceProvider extends ServiceProvider
return false;
});
DB::listen(function ($query) {
Log::info(
$query->sql,
[
'bindings' => $query->bindings,
'time' => $query->time,
]
);
});
}
}

View file

@ -1,5 +1,5 @@
{
"$schema": "https://next.shadcn-svelte.com/schema.json",
"$schema": "https://shadcn-svelte.com/schema.json",
"tailwind": {
"css": "resources/css/app.css",
"baseColor": "neutral"
@ -12,5 +12,5 @@
"lib": "@/lib"
},
"typescript": true,
"registry": "https://next.shadcn-svelte.com/registry"
"registry": "https://shadcn-svelte.com/registry"
}

View file

@ -17,7 +17,6 @@ return new class extends Migration
$table->string('name');
$table->text('description')->nullable();
$table->date('start_date');
$table->date('end_date');
$table->string('status')->default('planned');
$table->string('location')->nullable();
$table->integer('max_teams')->default(0);
@ -31,6 +30,11 @@ return new class extends Migration
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->integer('competition_id')->nullable();
$table->string('algorithm')->default('round_robin');
$table->json('config')->nullable();
$table->integer('sequence_order')->default(1);
$table->string('status')->default('active');
$table->timestamps();
});
@ -48,6 +52,13 @@ return new class extends Migration
$table->timestamps();
});
Schema::create('competition_field', function (Blueprint $table) {
$table->id();
$table->foreignId('competition_id')->constrained('competitions')->onDelete('cascade');
$table->foreignId('field_id')->constrained('fields')->onDelete('cascade');
$table->timestamps();
});
// Create teams table
Schema::create('teams', function (Blueprint $table) {
$table->id();
@ -59,12 +70,11 @@ return new class extends Migration
$table->timestamps();
});
// Create team members table
Schema::create('team_members', function (Blueprint $table) {
Schema::create('competition_team', function (Blueprint $table) {
$table->id();
$table->foreignId('competition_id')->constrained('competitions')->onDelete('cascade');
$table->foreignId('team_id')->constrained('teams')->onDelete('cascade');
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->string('role')->default('member');
$table->string('status')->default('confirmed');
$table->timestamps();
});
@ -103,7 +113,7 @@ return new class extends Migration
{
Schema::dropIfExists('break_periods');
Schema::dropIfExists('matches');
Schema::dropIfExists('team_members');
Schema::dropIfExists('competition_team');
Schema::dropIfExists('teams');
Schema::dropIfExists('fields');
Schema::dropIfExists('scheduling_modes');

View file

@ -87,11 +87,11 @@
<div class="flex flex-col gap-2 text-sm">
<div class="flex items-center gap-2">
<Calendar class="h-4 w-4 text-muted-foreground" />
<span>{formatDate(tournament.date)}</span>
<span>{formatDate(tournament.start_date)}</span>
</div>
<div class="flex items-center gap-2">
<Users class="h-4 w-4 text-muted-foreground" />
<span>{tournament.participantsCount} participants</span>
<span>{tournament.max_teams} participants</span>
</div>
</div>
</CardContent>
@ -107,7 +107,7 @@
</div>
</TabsContent>
<TabsContent value="my" class="mt-6">
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3 w-full">
{#if !userTournaments.original.data || userTournaments.original.data.length === 0}
<div class="flex flex-col items-center justify-center rounded-lg border border-dashed p-8 text-center">
<Trophy class="h-10 w-10 text-muted-foreground" />
@ -118,47 +118,45 @@
</Button>
</div>
{:else}
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{#each userTournaments.original.data as tournament (tournament.id)}
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Trophy class="h-5 w-5 text-primary" />
{tournament.name}
</CardTitle>
<CardDescription>{tournament.description}</CardDescription>
</CardHeader>
<CardContent>
<div class="flex flex-col gap-2 text-sm">
<div class="flex items-center gap-2">
<Calendar class="h-4 w-4 text-muted-foreground" />
<span>{formatDate(tournament.date)}</span>
</div>
<div class="flex items-center gap-2">
<Users class="h-4 w-4 text-muted-foreground" />
<span>{tournament.participantsCount} participants</span>
</div>
<div class="flex items-center gap-2">
<User class="h-4 w-4 text-muted-foreground" />
<span>Created by you</span>
</div>
{#each userTournaments.original.data as tournament (tournament.id)}
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Trophy class="h-5 w-5 text-primary" />
{tournament.name}
</CardTitle>
<CardDescription>{tournament.description}</CardDescription>
</CardHeader>
<CardContent>
<div class="flex flex-col gap-2 text-sm">
<div class="flex items-center gap-2">
<Calendar class="h-4 w-4 text-muted-foreground" />
<span>{formatDate(tournament.start_date)}</span>
</div>
</CardContent>
<CardFooter class="flex gap-2">
<Button variant="outline" class="flex-1">
<Link href={`/tournaments/${tournament.id}`} class="flex items-center justify-center w-full"
>View</Link
>
</Button>
<Button variant="secondary" class="flex-1">
<Link href={`/tournaments/${tournament.id}/edit`} class="flex items-center justify-center w-full">
Edit
</Link>
</Button>
</CardFooter>
</Card>
{/each}
</div>
<div class="flex items-center gap-2">
<Users class="h-4 w-4 text-muted-foreground" />
<span>{tournament.max_teams} participants</span>
</div>
<div class="flex items-center gap-2">
<User class="h-4 w-4 text-muted-foreground" />
<span>Created by you</span>
</div>
</div>
</CardContent>
<CardFooter class="flex gap-2">
<Button variant="outline" class="flex-1">
<Link href={`/tournaments/${tournament.id}`} class="flex items-center justify-center w-full"
>View</Link
>
</Button>
<Button class="flex-1">
<Link href={`/tournaments/${tournament.id}/edit`} class="flex items-center justify-center w-full">
Edit
</Link>
</Button>
</CardFooter>
</Card>
{/each}
{/if}
</div>
</TabsContent>

View file

@ -72,7 +72,6 @@
<AppLayout {breadcrumbs}>
<div class="container mx-auto py-10">
<h1 class="text-2xl font-bold mb-6">Create New Tournament</h1>
<div class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6">
<form onsubmit={handleSubmit} class="space-y-6">
<!-- Tournament Name -->

View file

@ -0,0 +1,55 @@
<script lang="ts">
import Button from '@/components/ui/button/button.svelte';
import * as Card from '@/components/ui/card/index.js';
import * as Table from '@/components/ui/table';
import AppLayout from '@/layouts/AppLayout.svelte';
import { type Tournament } from '@/types';
const breadcrumbs = [
{ title: 'Home', href: '/' },
{ title: 'Tournaments', href: '/tournaments' },
{ title: 'Tournament Name', href: '/tournaments/1' },
];
let { tournament }: { tournament: Tournament } = $props();
function handleAddTeam() {
// add a new row to write the team name, on write complete save it
}
</script>
<AppLayout {breadcrumbs}>
{JSON.stringify(tournament)}
<!-- Teams -->
<Card.Root>
<Card.Header>
<Card.Title>Teams</Card.Title>
</Card.Header>
<Card.Content>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head>Name</Table.Head>
<Table.Head>Actions</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each tournament.teams as team (team)}
<Table.Row>
<Table.Cell>{JSON.stringify(team)}</Table.Cell>
<Table.Cell>
<Button variant="outline">Edit</Button>
<Button variant="destructive">Delete</Button>
</Table.Cell>
</Table.Row>
{/each}
<Table.Row>
<Table.Cell>
<Button onsubmit={handleAddTeam} variant="outline">+ Add Team</Button>
</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
</Card.Content>
</Card.Root>
</AppLayout>

View file

@ -44,3 +44,27 @@ export interface Permission {
description: string;
is_wildcard: boolean;
}
export enum Status {
DRAFT = 'draft',
PUBLIC = 'public',
PRIVATE = 'private',
}
export interface Tournament {
id: number;
name: string;
description?: string;
start_date: string;
status: Status;
location?: string;
max_teams: number;
current_scheduling_mode_id?: number;
owner: string;
created_at: string;
updated_at: string;
teams: object[];
fields: object[];
matches: object[];
break_periods: object[];
}

View file

@ -11,7 +11,7 @@ Route::get('/', function () {
$user = Auth::user();
$userTournaments = [];
if ($user) {
$userTournaments = Inertia::defer(fn() => CompetitionController::getUser($user, 1, 10));
$userTournaments = Inertia::defer(fn() => CompetitionController::getUser($user, 0, 10));
}
return Inertia::render('Home', [
'publicTournaments' => Inertia::defer(fn() => CompetitionController::getPublics(1, 10)),