From 3b09f4baedf7b6a2c0cfc978e80f687e8913f384 Mon Sep 17 00:00:00 2001 From: ghassan Date: Sun, 15 Mar 2026 04:55:18 +0300 Subject: [PATCH] all is working great --- app/Http/Controllers/VideoController.php | 15 +- resources/views/videos/types/match.blade.php | 324 +++++++++++-------- 2 files changed, 202 insertions(+), 137 deletions(-) diff --git a/app/Http/Controllers/VideoController.php b/app/Http/Controllers/VideoController.php index 711df84..eccd69b 100644 --- a/app/Http/Controllers/VideoController.php +++ b/app/Http/Controllers/VideoController.php @@ -201,7 +201,7 @@ class VideoController extends Controller } // Load comments with user relationship - $video->load(['comments.user', 'comments.replies.user']); + $video->load(['comments.user', 'comments.replies.user', 'matchRounds.points', 'coachReviews']); // Handle playlist navigation if playlist parameter is provided $playlist = null; @@ -236,6 +236,19 @@ class VideoController extends Controller return view($view, compact('video', 'playlist', 'nextVideo', 'previousVideo', 'recommendedVideos', 'playlistVideos')); } + public function matchData(Video $video) + { + if (! $video->canView(Auth::user())) { + abort(403); + } + + return response()->json([ + 'success' => true, + 'rounds' => $video->matchRounds()->with('points')->orderBy('round_number')->get(), + 'reviews' => $video->coachReviews()->orderBy('start_time_seconds')->get(), + ]); + } + public function edit(Video $video, Request $request) { // Check if user owns the video diff --git a/resources/views/videos/types/match.blade.php b/resources/views/videos/types/match.blade.php index eab25c0..c6dcc91 100644 --- a/resources/views/videos/types/match.blade.php +++ b/resources/views/videos/types/match.blade.php @@ -507,6 +507,7 @@ } .action-btn, + .action-btn a, .comment-section .action-btn { border: none; border-radius: 8px; @@ -521,12 +522,15 @@ align-items: center; gap: 6px; transition: all 0.2s ease; + text-decoration: none; } .action-btn:hover, + .action-btn a:hover, .comment-section .action-btn:hover { background: var(--border-color); transform: translateY(-1px); + text-decoration: none; } .action-btn:active, @@ -1186,6 +1190,100 @@ color: white; } + /* ===== Coach Review Tab Specific Styles ===== */ + #tab-review .event-item { + display: flex; + gap: 12px; + width: 100%; + } + + #tab-review .event-time-container { + flex: 0 0 33.333%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + gap: 2px; + line-height: 1.2; + min-height: 60px; + } + + #tab-review .event-time { + font-weight: 600; + color: #3ea6ff; + font-size: 13px; + } + + #tab-review .event-label { + font-size: 15px; + font-weight: 600; + margin-bottom: 6px; + color: var(--text-primary); + } + + #tab-review .event-meta { + display: flex; + align-items: flex-start; + gap: 8px; + font-size: 13px; + line-height: 1.4; + color: var(--text-secondary); + } + + .comment-avatar { + width: 28px; + height: 28px; + border-radius: 50%; + overflow: hidden; + flex-shrink: 0; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + } + + .comment-avatar img { + width: 100%; + height: 100%; + object-fit: cover; + } + + #tab-review .event-meta-text { + flex: 1; + } + + #tab-review .event-meta-text .coach-name { + font-weight: 600; + color: var(--text-primary); + margin-bottom: 2px; + } + + #tab-review .event-meta-text .review-note { + color: var(--text-secondary); + font-size: 12.5px; + } + + /* Review item hover improvements */ + #tab-review .event-item:hover { + background: rgba(255, 255, 255, 0.05); + border-radius: 10px; + padding: 12px; + margin: -2px 0 -2px -8px; + border-left: 3px solid var(--brand-red); + } + + /* Ensure consistency with points tab on mobile */ + @media (max-width: 768px) { + #tab-review .event-item { + flex-direction: column; + gap: 8px; + } + + #tab-review .event-time { + align-self: flex-start; + order: -1; + } + } + /* ===== Match Modal Styles ===== */ .match-modal-overlay { display: none; @@ -1919,15 +2017,32 @@ }); } - // Match Highlights Toggle + // Match Highlights Toggle with localStorage persistence document.addEventListener('DOMContentLoaded', function() { const toggleBtn = document.getElementById('matchHighlightsToggle'); const sidebar = document.querySelector('.events-sidebar'); + const videoId = {{ isset($video) ? $video->id : 0 }}; if (toggleBtn && sidebar) { + // Load saved state on page load + const savedState = localStorage.getItem(`highlights_${videoId}`); + if (savedState === 'open') { + sidebar.classList.add('show'); + toggleBtn.classList.add('expanded'); + toggleBtn.textContent = 'Highlights'; + } + toggleBtn.addEventListener('click', function() { - sidebar.classList.toggle('show'); + const isOpen = sidebar.classList.toggle('show'); toggleBtn.classList.toggle('expanded'); + + // Save state to localStorage + if (isOpen) { + localStorage.setItem(`highlights_${videoId}`, 'open'); + toggleBtn.textContent = 'Highlights'; + } else { + localStorage.removeItem(`highlights_${videoId}`); + } }); } }); @@ -2294,20 +2409,21 @@
-
-
🔥
+
+
🔥
@00:32–00:50
-
- Good pressure, but guard too low after first kick -
+
🔥 Good pressure, but guard too low after first kick
Coach Ahmed
- Coach Ahmed • Hands drop after scoring, drill guard recovery immediately. +
+
Coach Ahmed
+
Hands drop after scoring, drill guard recovery + immediately.
+
@auth @@ -2322,15 +2438,21 @@ @endauth
-
-
🤔
+
+
🤔
@02:05–02:20
-
Missed counter opportunity
+
🤔 Missed counter opportunity
- Coach Sara • Great angle, but no follow up. Use this clip to discuss risk vs reward. +
+ Coach Sara +
+
+
Coach Sara
+
Great angle, but no follow up. Use this clip to + discuss risk vs reward.
+
@auth @@ -2345,21 +2467,21 @@ @endauth
-
-
😄
+
+
😄
@03:25
-
- Excellent angle change and follow-up -
+
😄 Excellent angle change and follow-up
Coach Ahmed
- Coach Ahmed • Save as positive highlight, ideal example of exit and re-entry after - scoring. +
+
Coach Ahmed
+
Save as positive highlight, ideal example of exit and + re-entry after scoring.
+
@auth @@ -2693,111 +2815,38 @@ try { const response = await fetch(`/videos/${videoId}/match-data`); const data = await response.json(); - if (data.success && data.rounds && data.rounds.length > 0) { - renderMatchData(data.rounds, data.reviews); + if (data.success) { + renderMatchData(data.rounds || [], data.reviews || []); } else { + console.warn('No match data:', data.message); renderStaticData(); } } catch (error) { - renderStaticData(); + console.error('Failed to load match data:', error); + // Try to use server-side data first + if (window.matchRounds && window.matchReviews) { + renderMatchData(window.matchRounds, window.matchReviews); + } else { + renderStaticData(); + } } } + // Pass server-side data to JS if available + window.matchRounds = @json($video->matchRounds ?? []); + window.matchReviews = @json($video->coachReviews ?? []); + if (window.matchRounds.length > 0) { + renderMatchData(window.matchRounds, window.matchReviews); + } + function renderStaticData() { - const staticRounds = [{ - id: 1, - round_number: 1, - name: 'ROUND 1', - points: [{ - id: 1, - timestamp_seconds: 20, - action: 'Blue body kick', - points: 1, - competitor: 'blue', - score_blue: 1, - score_red: 0, - notes: 'Clean contact.' - }, - { - id: 2, - timestamp_seconds: 45, - action: 'Blue scores body kick', - points: 1, - competitor: 'blue', - score_blue: 2, - score_red: 0, - notes: 'Judge: Central.' - } - ] - }, - { - id: 2, - round_number: 2, - name: 'ROUND 2', - points: [{ - id: 3, - timestamp_seconds: 90, - action: 'Red head kick', - points: 3, - competitor: 'red', - score_blue: 2, - score_red: 3, - notes: 'Video replay requested.' - }] - }, - { - id: 3, - round_number: 3, - name: 'ROUND 3', - points: [{ - id: 4, - timestamp_seconds: 150, - action: 'Blue spinning backfist', - points: 2, - competitor: 'blue', - score_blue: 4, - score_red: 3, - notes: 'Close round.' - }, - { - id: 5, - timestamp_seconds: 180, - action: 'Red leg kick', - points: 1, - competitor: 'red', - score_blue: 4, - score_red: 4, - notes: 'Tied!' - } - ] - } - ]; - const staticReviews = [{ - id: 1, - start_time_seconds: 32, - end_time_seconds: 50, - note: 'Good pressure, but guard too low after first kick', - coach_name: 'Coach Ahmed', - emoji: '🔥' - }, - { - id: 2, - start_time_seconds: 125, - end_time_seconds: 140, - note: 'Missed counter opportunity', - coach_name: 'Coach Sara', - emoji: '🤔' - }, - { - id: 3, - start_time_seconds: 205, - end_time_seconds: null, - note: 'Excellent angle change and follow-up', - coach_name: 'Coach Ahmed', - emoji: '😄' - } - ]; - renderMatchData(staticRounds, staticReviews); + // No static demo data - empty state + const pointsContainer = document.getElementById('officialEvents'); + const reviewsContainer = document.getElementById('reviewEvents'); + if (pointsContainer) pointsContainer.innerHTML = + '
No match data yet.
'; + if (reviewsContainer) reviewsContainer.innerHTML = + '
No coach notes yet.
'; } function renderMatchData(rounds, reviews) { @@ -2812,12 +2861,12 @@
${round.name} ${isOwner ? ` -
- - - -
- ` : ''} +
+ + + +
+ ` : ''}
`; if (round.points) { @@ -2838,10 +2887,10 @@
${isOwner ? ` -
- - -
` : ''} +
+ + +
` : ''}
`; }); } @@ -2868,10 +2917,10 @@
${isOwner ? ` -
- - -
` : ''} +
+ + +
` : ''} `; }); } @@ -2923,11 +2972,14 @@ document.querySelectorAll('.event-item').forEach(item => { item.addEventListener('click', function(e) { if (e.target.closest('.event-actions')) return; - const timeStart = this.getAttribute('data-time-start'); - if (timeStart) { + const timeStart = parseFloat(this.getAttribute('data-time-start')); + const timeEnd = parseFloat(this.getAttribute('data-time-end') || 0); + + if (timeStart || timeStart === 0) { const videoPlayer = document.getElementById('videoPlayer'); if (videoPlayer) { - videoPlayer.currentTime = parseInt(timeStart); + // Start 1 second early for context + videoPlayer.currentTime = Math.max(0, timeStart - 1.0); videoPlayer.play(); } }