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/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',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return $this->belongsTo(Video::class);
|
||||
|
||||
@ -434,6 +434,12 @@ class Video extends Model
|
||||
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)
|
||||
public function getRecentViews($hours = 48)
|
||||
{
|
||||
|
||||
@ -91,6 +91,16 @@
|
||||
<div class="invalid-feedback" data-field="match_time"></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>
|
||||
|
||||
{{-- Participants --}}
|
||||
@ -126,9 +136,17 @@
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<x-sports-image name="media_referee_photo" label="Photo" class="sm-img-avatar" />
|
||||
<div class="flex-grow-1">
|
||||
<div class="row g-3">
|
||||
<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>
|
||||
|
||||
@ -196,7 +214,7 @@
|
||||
<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>
|
||||
<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>
|
||||
<input type="text" class="form-control" name="participants[p1_role]" placeholder="home, away, red, blue…"></div>
|
||||
</div>
|
||||
@ -207,7 +225,7 @@
|
||||
<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>
|
||||
<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>
|
||||
<input type="text" class="form-control" name="participants[p2_role]" placeholder="home, away, red, blue…"></div>
|
||||
</div>
|
||||
|
||||
@ -2014,62 +2014,10 @@
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
// ===== DUMMY DATA FALLBACKS =====
|
||||
$blueFighter =
|
||||
$blueFighter ??
|
||||
(object) [
|
||||
'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',
|
||||
];
|
||||
// Real match data (fighters, referee, championship, match#/court, venue)
|
||||
// for this video. null when no SportsMatch record exists — the header then
|
||||
// degrades to just the title/views/date instead of showing demo content.
|
||||
$hd = $video->sportsMatch?->headerData();
|
||||
@endphp
|
||||
|
||||
<!-- Video Layout Container -->
|
||||
@ -2096,52 +2044,70 @@
|
||||
<div class="event-header">
|
||||
<div class="event-header-left">
|
||||
<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 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">
|
||||
<i class="bi bi-trophy trophy-icon" title="Match Type"></i>
|
||||
<span>{{ $video->title }} –</span>
|
||||
<i class="bi bi-trophy trophy-icon" title="Sports match"></i>
|
||||
<span>{{ $video->title }}@if ($hasBlue || $hasRed) –@endif</span>
|
||||
@if ($hasBlue)
|
||||
<span style="color: #2563eb; font-weight: bold">
|
||||
<span class="fi fi-{{ $blueFighter->flag ?? 'bh' }}" style="width:20px;height:15px;border-radius:2px;display:inline-block;vertical-align:middle;"></span>
|
||||
<a href="https://takeone-dev.innovator.bh/member/{{ $blueFighter->member_id ?? 5 }}"
|
||||
title="View {{ $blueFighter->name ?? 'Sami Al Manea' }}'s profile">
|
||||
{{ $blueFighter->name ?? 'Sami Al Manea' }}
|
||||
</a>
|
||||
@if ($hd['blue']['flag'])<span class="fi fi-{{ $hd['blue']['flag'] }}" style="{{ $flagStyle }}"></span>@endif
|
||||
{{ $hd['blue']['name'] }}
|
||||
</span>
|
||||
<span>vs</span>
|
||||
@endif
|
||||
@if ($hasBlue && $hasRed)<span>vs</span>@endif
|
||||
@if ($hasRed)
|
||||
<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>
|
||||
<a href="https://takeone-dev.innovator.bh/member/{{ $redFighter->member_id ?? 11 }}"
|
||||
title="View {{ $redFighter->name ?? 'Hassan Al Shallan' }}'s profile">
|
||||
{{ $redFighter->name ?? 'Hassan Al Shallan' }}
|
||||
</a>
|
||||
@if ($hd['red']['flag'])<span class="fi fi-{{ $hd['red']['flag'] }}" style="{{ $flagStyle }}"></span>@endif
|
||||
{{ $hd['red']['name'] }}
|
||||
</span>
|
||||
<span>({{ $match->weight_category ?? 'U18' }})</span>
|
||||
@endif
|
||||
@if ($hd && $hd['weight_category'])<span>({{ $hd['weight_category'] }})</span>@endif
|
||||
</h1>
|
||||
|
||||
<!-- Meta: Championship Link • Match# • Court • Referee (all inline) -->
|
||||
<a class="meta-link" href="{{ $championship->url ?? '/championship/2026' }}"
|
||||
style="white-space: nowrap; display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; line-height: 1.4;">
|
||||
<span
|
||||
style="color: #ffffff; font-weight: bold; font-size: 0.85rem;">{{ $championship->name ?? 'Championship 2026' }}</span>
|
||||
<span style="color: var(--text-secondary);">•</span>
|
||||
<span style="color: #ff00dd; font-weight: bold; font-size: 0.85rem;">Match#
|
||||
{{ $match->number ?? '103' }}</span>
|
||||
<span style="color: var(--text-secondary);">•</span>
|
||||
<span style="color: #00af09; font-weight: bold; font-size: 0.85rem;">Court
|
||||
{{ $match->court ?? '2' }}</span>
|
||||
<span style="color: var(--text-secondary);">•</span>
|
||||
<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' }}
|
||||
{{-- Inline meta: championship • Match# • Court • referee • views/date.
|
||||
Only non-empty pieces render; views/date always show. --}}
|
||||
<div class="meta-link"
|
||||
style="display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; line-height: 1.4;">
|
||||
@foreach ($metaPieces as $piece)
|
||||
@if (!$loop->first)<span style="color: var(--text-secondary);">•</span>@endif
|
||||
@if ($piece['type'] === 'champ')
|
||||
<span style="color: #ffffff; font-weight: bold; font-size: 0.85rem;">{{ $piece['val'] }}</span>
|
||||
@elseif ($piece['type'] === 'num')
|
||||
<span style="color: #ff00dd; font-weight: bold; font-size: 0.85rem;">Match# {{ $piece['val'] }}</span>
|
||||
@elseif ($piece['type'] === 'court')
|
||||
<span style="color: #00af09; font-weight: bold; font-size: 0.85rem;">Court {{ $piece['val'] }}</span>
|
||||
@elseif ($piece['type'] === 'ref')
|
||||
<span style="color: #ffd900; font-weight: bold; font-size: 0.85rem; display: inline-flex; align-items: center; gap: 2px;">
|
||||
@if ($piece['val']['flag'])<span class="fi fi-{{ $piece['val']['flag'] }}" style="{{ $flagStyle }}"></span>@endif
|
||||
{{ $piece['val']['name'] }}
|
||||
</span>
|
||||
<span
|
||||
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>
|
||||
@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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user