Wire match header to real SportsMatch data; add Match#/Court + country flags
The match page header rendered hard-coded demo content (Sami Al Manea vs Hassan Al Shallan, Match# 103, referee Ghassan Yusuf, external takeone-dev links) on EVERY match video, ignoring the SportsMatch record the create form saves. - Video::sportsMatch() hasOne relationship. - SportsMatch::headerData() maps the record into the header's shape (fighters + country flags, championship, weight class, referee, venue, match#/court), null-safe, with a country-name/ISO2 -> flag-icons resolver. - match.blade header now renders from headerData(): real names/flags, no external links, and degrades gracefully to just title/views/date when a video has no match data (instead of the fake card). - sports-match-modal: add Match # and Court fields (stored in the existing competition JSON, no migration) and convert fighter/referee country inputs to the mandated <x-country-select> so header flags resolve from ISO2. - Tracker updated for the new country-select usages. Verified: empty state (no SportsMatch) shows only title/views/date; a populated record renders fighters, flags (bh/us), championship, Match# 42, Court 3, U21, referee; modal compiles with the new fields. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8ea63a6720
commit
af276f2cd1
@ -144,6 +144,9 @@ Stored value: ISO2 code (e.g. `"BH"`).
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `resources/views/user/profile.blade.php` | `nationality` | Edit Profile form |
|
| `resources/views/user/profile.blade.php` | `nationality` | Edit Profile form |
|
||||||
| `resources/views/auth/register.blade.php` | `nationality` | Registration form — mandatory |
|
| `resources/views/auth/register.blade.php` | `nationality` | Registration form — mandatory |
|
||||||
|
| `resources/views/layouts/partials/sports-match-modal.blade.php` | `participants[p1_country]` | Fighter 1 country → header flag |
|
||||||
|
| `resources/views/layouts/partials/sports-match-modal.blade.php` | `participants[p2_country]` | Fighter 2 country → header flag |
|
||||||
|
| `resources/views/layouts/partials/sports-match-modal.blade.php` | `participants[referee_country]` | Referee country → header flag |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,63 @@ class SportsMatch extends Model
|
|||||||
'reviews' => 'array',
|
'reviews' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shape the record into exactly what the match page header renders. Every value
|
||||||
|
* is null-safe so a video with no/partial match data degrades to just its title
|
||||||
|
* instead of falling back to hard-coded demo content.
|
||||||
|
*/
|
||||||
|
public function headerData(): array
|
||||||
|
{
|
||||||
|
$p = $this->participants ?? [];
|
||||||
|
$c = $this->competition ?? [];
|
||||||
|
$v = $this->venue ?? [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'blue' => [
|
||||||
|
'name' => $this->participant1_name ?: null,
|
||||||
|
'club' => $p['p1_club'] ?? null,
|
||||||
|
'flag' => $this->flagFor($p['p1_country'] ?? null),
|
||||||
|
],
|
||||||
|
'red' => [
|
||||||
|
'name' => $this->participant2_name ?: null,
|
||||||
|
'club' => $p['p2_club'] ?? null,
|
||||||
|
'flag' => $this->flagFor($p['p2_country'] ?? null),
|
||||||
|
],
|
||||||
|
'weight_category' => $p['weight_class'] ?? null,
|
||||||
|
'championship' => $c['championship_name'] ?? ($this->event_name ?: null),
|
||||||
|
'match_number' => $c['match_number'] ?? null,
|
||||||
|
'court' => $c['court'] ?? null,
|
||||||
|
'referee' => [
|
||||||
|
'name' => $this->referee_name ?: null,
|
||||||
|
'flag' => $this->flagFor($p['referee_country'] ?? null),
|
||||||
|
],
|
||||||
|
'venue' => [
|
||||||
|
'name' => $this->venue_name ?: ($v['name'] ?? null),
|
||||||
|
'map_link' => $v['map_link'] ?? null,
|
||||||
|
],
|
||||||
|
'event_logo' => $this->media['event_poster'] ?? null, // rel path or null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resolve a country name or ISO2 code to a lowercase flag-icons code (or null). */
|
||||||
|
private function flagFor(?string $country): ?string
|
||||||
|
{
|
||||||
|
$country = trim((string) $country);
|
||||||
|
if ($country === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$countries = \App\Data\Countries::all();
|
||||||
|
if (preg_match('/^[A-Za-z]{2}$/', $country) && isset($countries[strtoupper($country)])) {
|
||||||
|
return strtolower($country);
|
||||||
|
}
|
||||||
|
foreach ($countries as $iso2 => $meta) {
|
||||||
|
if (strcasecmp($meta['name'], $country) === 0) {
|
||||||
|
return strtolower($iso2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public function video(): BelongsTo
|
public function video(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Video::class);
|
return $this->belongsTo(Video::class);
|
||||||
|
|||||||
@ -434,6 +434,12 @@ class Video extends Model
|
|||||||
return $this->hasMany(CoachReview::class)->orderBy('start_time_seconds');
|
return $this->hasMany(CoachReview::class)->orderBy('start_time_seconds');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The sports match record backing a type=match video (fighters, referee, etc.)
|
||||||
|
public function sportsMatch()
|
||||||
|
{
|
||||||
|
return $this->hasOne(SportsMatch::class);
|
||||||
|
}
|
||||||
|
|
||||||
// Get recent views count (within hours)
|
// Get recent views count (within hours)
|
||||||
public function getRecentViews($hours = 48)
|
public function getRecentViews($hours = 48)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -91,6 +91,16 @@
|
|||||||
<div class="invalid-feedback" data-field="match_time"></div>
|
<div class="invalid-feedback" data-field="match_time"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row g-3 mt-0">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<label for="sm-match-number" class="form-label">Match #</label>
|
||||||
|
<input type="text" class="form-control" name="competition[match_number]" id="sm-match-number" placeholder="e.g. 103">
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<label for="sm-court" class="form-label">Court / field</label>
|
||||||
|
<input type="text" class="form-control" name="competition[court]" id="sm-court" placeholder="e.g. 2">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Participants --}}
|
{{-- Participants --}}
|
||||||
@ -126,8 +136,16 @@
|
|||||||
<div class="d-flex align-items-center gap-3">
|
<div class="d-flex align-items-center gap-3">
|
||||||
<x-sports-image name="media_referee_photo" label="Photo" class="sm-img-avatar" />
|
<x-sports-image name="media_referee_photo" label="Photo" class="sm-img-avatar" />
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<label for="sm-referee" class="form-label">Referee name</label>
|
<div class="row g-3">
|
||||||
<input type="text" class="form-control" name="referee_name" id="sm-referee" placeholder="Referee name">
|
<div class="col-sm-7">
|
||||||
|
<label for="sm-referee" class="form-label">Referee name</label>
|
||||||
|
<input type="text" class="form-control" name="referee_name" id="sm-referee" placeholder="Referee name">
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<label class="form-label">Country</label>
|
||||||
|
<x-country-select name="participants[referee_country]" placeholder="Country" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -196,7 +214,7 @@
|
|||||||
<div class="col-sm-6"><label class="form-label">Type</label>
|
<div class="col-sm-6"><label class="form-label">Type</label>
|
||||||
<input type="text" class="form-control" name="participants[p1_type]" placeholder="Individual / Team / Pair"></div>
|
<input type="text" class="form-control" name="participants[p1_type]" placeholder="Individual / Team / Pair"></div>
|
||||||
<div class="col-sm-6"><label class="form-label">Country</label>
|
<div class="col-sm-6"><label class="form-label">Country</label>
|
||||||
<input type="text" class="form-control" name="participants[p1_country]"></div>
|
<x-country-select name="participants[p1_country]" placeholder="Select country" /></div>
|
||||||
<div class="col-sm-6"><label class="form-label">Role</label>
|
<div class="col-sm-6"><label class="form-label">Role</label>
|
||||||
<input type="text" class="form-control" name="participants[p1_role]" placeholder="home, away, red, blue…"></div>
|
<input type="text" class="form-control" name="participants[p1_role]" placeholder="home, away, red, blue…"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -207,7 +225,7 @@
|
|||||||
<div class="col-sm-6"><label class="form-label">Type</label>
|
<div class="col-sm-6"><label class="form-label">Type</label>
|
||||||
<input type="text" class="form-control" name="participants[p2_type]" placeholder="Individual / Team / Pair"></div>
|
<input type="text" class="form-control" name="participants[p2_type]" placeholder="Individual / Team / Pair"></div>
|
||||||
<div class="col-sm-6"><label class="form-label">Country</label>
|
<div class="col-sm-6"><label class="form-label">Country</label>
|
||||||
<input type="text" class="form-control" name="participants[p2_country]"></div>
|
<x-country-select name="participants[p2_country]" placeholder="Select country" /></div>
|
||||||
<div class="col-sm-6"><label class="form-label">Role</label>
|
<div class="col-sm-6"><label class="form-label">Role</label>
|
||||||
<input type="text" class="form-control" name="participants[p2_role]" placeholder="home, away, red, blue…"></div>
|
<input type="text" class="form-control" name="participants[p2_role]" placeholder="home, away, red, blue…"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2014,62 +2014,10 @@
|
|||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
@php
|
@php
|
||||||
// ===== DUMMY DATA FALLBACKS =====
|
// Real match data (fighters, referee, championship, match#/court, venue)
|
||||||
$blueFighter =
|
// for this video. null when no SportsMatch record exists — the header then
|
||||||
$blueFighter ??
|
// degrades to just the title/views/date instead of showing demo content.
|
||||||
(object) [
|
$hd = $video->sportsMatch?->headerData();
|
||||||
'name' => 'Sami Al Manea',
|
|
||||||
'flag' => 'bh',
|
|
||||||
'photo' => 'https://takeone-dev.innovator.bh/storage/images/profiles/profile_5.png?v=1773157950',
|
|
||||||
'team' => 'Emperor TaeKwonDo Academy',
|
|
||||||
'country' => 'Bahrain',
|
|
||||||
'logo' => 'https://takeone-dev.innovator.bh/storage/clubs/1/branding/logo_1771488666.png',
|
|
||||||
'member_id' => 5,
|
|
||||||
'club_url' => 'https://takeone-dev.innovator.bh/mobile/eta',
|
|
||||||
];
|
|
||||||
$redFighter =
|
|
||||||
$redFighter ??
|
|
||||||
(object) [
|
|
||||||
'name' => 'Hassan Al Shallan',
|
|
||||||
'flag' => 'bh',
|
|
||||||
'photo' => 'https://takeone-dev.innovator.bh/storage/images/profiles/profile_11.png?t=1773268056907',
|
|
||||||
'team' => 'Legend TaeKwonDo Academy',
|
|
||||||
'country' => 'Bahrain',
|
|
||||||
'logo' => 'https://takeone-dev.innovator.bh/storage/clubs/logos/logo_1773267551.png',
|
|
||||||
'member_id' => 11,
|
|
||||||
'club_url' => 'https://takeone-dev.innovator.bh/clubs/legend-taekwondo-academy',
|
|
||||||
];
|
|
||||||
$match =
|
|
||||||
$match ??
|
|
||||||
(object) [
|
|
||||||
'number' => '103',
|
|
||||||
'court' => '2',
|
|
||||||
'weight_category' => 'U18',
|
|
||||||
'score_blue' => 7,
|
|
||||||
'score_red' => 5,
|
|
||||||
'features' =>
|
|
||||||
'Electronic scoring system (PSS & head‑gear) • Video replay available for head kicks and gam-jeom appeals',
|
|
||||||
];
|
|
||||||
$referee =
|
|
||||||
$referee ??
|
|
||||||
(object) [
|
|
||||||
'name' => 'Ghassan Yusuf',
|
|
||||||
'flag' => 'bh',
|
|
||||||
'photo' => 'https://takeone-dev.innovator.bh/storage/images/profiles/profile_8.png?t=1773267305519',
|
|
||||||
'member_id' => 4,
|
|
||||||
];
|
|
||||||
$championship =
|
|
||||||
$championship ??
|
|
||||||
(object) [
|
|
||||||
'name' => 'Championship 2026',
|
|
||||||
'url' => '/championship/2026',
|
|
||||||
];
|
|
||||||
$venue =
|
|
||||||
$venue ??
|
|
||||||
(object) [
|
|
||||||
'name' => 'Manama Sports Hall',
|
|
||||||
'map_link' => 'https://maps.google.com/?q=Manama+Sports+Hall+Bahrain',
|
|
||||||
];
|
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<!-- Video Layout Container -->
|
<!-- Video Layout Container -->
|
||||||
@ -2096,52 +2044,70 @@
|
|||||||
<div class="event-header">
|
<div class="event-header">
|
||||||
<div class="event-header-left">
|
<div class="event-header-left">
|
||||||
<div class="event-logo">
|
<div class="event-logo">
|
||||||
<img src="https://picsum.photos/seed/event-logo/200/200" alt="Event logo">
|
@if ($hd && $hd['event_logo'])
|
||||||
|
<img src="{{ route('media.thumbnail', $hd['event_logo']) }}" alt="Event logo">
|
||||||
|
@else
|
||||||
|
<i class="bi bi-trophy" style="font-size: 26px; color: var(--text-secondary);"></i>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="event-text">
|
<div class="event-text">
|
||||||
<!-- Title with Trophy Icon BEFORE "Taekwondo Finals" -->
|
@php
|
||||||
|
$flagStyle = 'width:20px;height:15px;border-radius:2px;display:inline-block;vertical-align:middle;';
|
||||||
|
$hasBlue = $hd && $hd['blue']['name'];
|
||||||
|
$hasRed = $hd && $hd['red']['name'];
|
||||||
|
// Ordered inline meta pieces — only the ones that actually have data.
|
||||||
|
$metaPieces = [];
|
||||||
|
if ($hd) {
|
||||||
|
if ($hd['championship']) $metaPieces[] = ['type' => 'champ', 'val' => $hd['championship']];
|
||||||
|
if ($hd['match_number']) $metaPieces[] = ['type' => 'num', 'val' => $hd['match_number']];
|
||||||
|
if ($hd['court']) $metaPieces[] = ['type' => 'court', 'val' => $hd['court']];
|
||||||
|
if ($hd['referee']['name']) $metaPieces[] = ['type' => 'ref', 'val' => $hd['referee']];
|
||||||
|
}
|
||||||
|
@endphp
|
||||||
|
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
<i class="bi bi-trophy trophy-icon" title="Match Type"></i>
|
<i class="bi bi-trophy trophy-icon" title="Sports match"></i>
|
||||||
<span>{{ $video->title }} –</span>
|
<span>{{ $video->title }}@if ($hasBlue || $hasRed) –@endif</span>
|
||||||
<span style="color: #2563eb; font-weight: bold">
|
@if ($hasBlue)
|
||||||
<span class="fi fi-{{ $blueFighter->flag ?? 'bh' }}" style="width:20px;height:15px;border-radius:2px;display:inline-block;vertical-align:middle;"></span>
|
<span style="color: #2563eb; font-weight: bold">
|
||||||
<a href="https://takeone-dev.innovator.bh/member/{{ $blueFighter->member_id ?? 5 }}"
|
@if ($hd['blue']['flag'])<span class="fi fi-{{ $hd['blue']['flag'] }}" style="{{ $flagStyle }}"></span>@endif
|
||||||
title="View {{ $blueFighter->name ?? 'Sami Al Manea' }}'s profile">
|
{{ $hd['blue']['name'] }}
|
||||||
{{ $blueFighter->name ?? 'Sami Al Manea' }}
|
</span>
|
||||||
</a>
|
@endif
|
||||||
</span>
|
@if ($hasBlue && $hasRed)<span>vs</span>@endif
|
||||||
<span>vs</span>
|
@if ($hasRed)
|
||||||
<span style="color: #ef4444; font-weight: bold">
|
<span style="color: #ef4444; font-weight: bold">
|
||||||
<span class="fi fi-{{ $redFighter->flag ?? 'bh' }}" style="width:20px;height:15px;border-radius:2px;display:inline-block;vertical-align:middle;"></span>
|
@if ($hd['red']['flag'])<span class="fi fi-{{ $hd['red']['flag'] }}" style="{{ $flagStyle }}"></span>@endif
|
||||||
<a href="https://takeone-dev.innovator.bh/member/{{ $redFighter->member_id ?? 11 }}"
|
{{ $hd['red']['name'] }}
|
||||||
title="View {{ $redFighter->name ?? 'Hassan Al Shallan' }}'s profile">
|
</span>
|
||||||
{{ $redFighter->name ?? 'Hassan Al Shallan' }}
|
@endif
|
||||||
</a>
|
@if ($hd && $hd['weight_category'])<span>({{ $hd['weight_category'] }})</span>@endif
|
||||||
</span>
|
|
||||||
<span>({{ $match->weight_category ?? 'U18' }})</span>
|
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<!-- Meta: Championship Link • Match# • Court • Referee (all inline) -->
|
{{-- Inline meta: championship • Match# • Court • referee • views/date.
|
||||||
<a class="meta-link" href="{{ $championship->url ?? '/championship/2026' }}"
|
Only non-empty pieces render; views/date always show. --}}
|
||||||
style="white-space: nowrap; display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; line-height: 1.4;">
|
<div class="meta-link"
|
||||||
<span
|
style="display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; line-height: 1.4;">
|
||||||
style="color: #ffffff; font-weight: bold; font-size: 0.85rem;">{{ $championship->name ?? 'Championship 2026' }}</span>
|
@foreach ($metaPieces as $piece)
|
||||||
<span style="color: var(--text-secondary);">•</span>
|
@if (!$loop->first)<span style="color: var(--text-secondary);">•</span>@endif
|
||||||
<span style="color: #ff00dd; font-weight: bold; font-size: 0.85rem;">Match#
|
@if ($piece['type'] === 'champ')
|
||||||
{{ $match->number ?? '103' }}</span>
|
<span style="color: #ffffff; font-weight: bold; font-size: 0.85rem;">{{ $piece['val'] }}</span>
|
||||||
<span style="color: var(--text-secondary);">•</span>
|
@elseif ($piece['type'] === 'num')
|
||||||
<span style="color: #00af09; font-weight: bold; font-size: 0.85rem;">Court
|
<span style="color: #ff00dd; font-weight: bold; font-size: 0.85rem;">Match# {{ $piece['val'] }}</span>
|
||||||
{{ $match->court ?? '2' }}</span>
|
@elseif ($piece['type'] === 'court')
|
||||||
<span style="color: var(--text-secondary);">•</span>
|
<span style="color: #00af09; font-weight: bold; font-size: 0.85rem;">Court {{ $piece['val'] }}</span>
|
||||||
<span
|
@elseif ($piece['type'] === 'ref')
|
||||||
style="color: #ffd900; font-weight: bold; font-size: 0.85rem; display: inline-flex; align-items: center; gap: 2px;">
|
<span style="color: #ffd900; font-weight: bold; font-size: 0.85rem; display: inline-flex; align-items: center; gap: 2px;">
|
||||||
<span class="fi fi-{{ $referee->flag ?? 'bh' }}" style="width:20px;height:15px;border-radius:2px;display:inline-block;vertical-align:middle;"></span> {{ $referee->name ?? 'Ghassan Yusuf' }}
|
@if ($piece['val']['flag'])<span class="fi fi-{{ $piece['val']['flag'] }}" style="{{ $flagStyle }}"></span>@endif
|
||||||
|
{{ $piece['val']['name'] }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@if (count($metaPieces))<span style="color: var(--text-secondary);">•</span>@endif
|
||||||
|
<span style="color: var(--text-secondary); font-weight: bold; font-size: 0.85rem;">
|
||||||
|
{{ number_format($video->view_count) }} views • {{ $video->created_at->format('M d, Y') }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
</div>
|
||||||
style="color: var(--text-secondary); font-weight: bold; font-size: 0.85rem; display: inline-flex; align-items: center; gap: 2px;">
|
|
||||||
- {{ number_format($video->view_count) }} views •
|
|
||||||
{{ $video->created_at->format('M d, Y') }}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user