all is working great
This commit is contained in:
parent
f850f40f78
commit
3b09f4baed
@ -201,7 +201,7 @@ class VideoController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load comments with user relationship
|
// 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
|
// Handle playlist navigation if playlist parameter is provided
|
||||||
$playlist = null;
|
$playlist = null;
|
||||||
@ -236,6 +236,19 @@ class VideoController extends Controller
|
|||||||
return view($view, compact('video', 'playlist', 'nextVideo', 'previousVideo', 'recommendedVideos', 'playlistVideos'));
|
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)
|
public function edit(Video $video, Request $request)
|
||||||
{
|
{
|
||||||
// Check if user owns the video
|
// Check if user owns the video
|
||||||
|
|||||||
@ -507,6 +507,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-btn,
|
.action-btn,
|
||||||
|
.action-btn a,
|
||||||
.comment-section .action-btn {
|
.comment-section .action-btn {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -521,12 +522,15 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn:hover,
|
.action-btn:hover,
|
||||||
|
.action-btn a:hover,
|
||||||
.comment-section .action-btn:hover {
|
.comment-section .action-btn:hover {
|
||||||
background: var(--border-color);
|
background: var(--border-color);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn:active,
|
.action-btn:active,
|
||||||
@ -1186,6 +1190,100 @@
|
|||||||
color: white;
|
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 Styles ===== */
|
||||||
.match-modal-overlay {
|
.match-modal-overlay {
|
||||||
display: none;
|
display: none;
|
||||||
@ -1919,15 +2017,32 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match Highlights Toggle
|
// Match Highlights Toggle with localStorage persistence
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const toggleBtn = document.getElementById('matchHighlightsToggle');
|
const toggleBtn = document.getElementById('matchHighlightsToggle');
|
||||||
const sidebar = document.querySelector('.events-sidebar');
|
const sidebar = document.querySelector('.events-sidebar');
|
||||||
|
const videoId = {{ isset($video) ? $video->id : 0 }};
|
||||||
|
|
||||||
if (toggleBtn && sidebar) {
|
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() {
|
toggleBtn.addEventListener('click', function() {
|
||||||
sidebar.classList.toggle('show');
|
const isOpen = sidebar.classList.toggle('show');
|
||||||
toggleBtn.classList.toggle('expanded');
|
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 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="event-list" id="reviewEvents">
|
<div class="event-list" id="reviewEvents">
|
||||||
<div class="event-item" data-time-start="32" data-time-end="50" data-id="rev1">
|
<div class="event-item" data-time-start="32" data-time-end="50" data-id="rev1">
|
||||||
<div
|
<div class="event-time-container">
|
||||||
style="display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
<div class="emoji">🔥</div>
|
||||||
<div style="font-size: 1.2rem; margin-bottom: 2px">🔥</div>
|
|
||||||
<div class="event-time">@00:32–00:50</div>
|
<div class="event-time">@00:32–00:50</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="event-label">
|
<div class="event-label">🔥 Good pressure, but guard too low after first kick</div>
|
||||||
Good pressure, but guard too low after first kick
|
|
||||||
</div>
|
|
||||||
<div class="event-meta">
|
<div class="event-meta">
|
||||||
<div class="comment-avatar">
|
<div class="comment-avatar">
|
||||||
<img src="https://picsum.photos/seed/coach-ahmed/80/80" alt="Coach Ahmed">
|
<img src="https://picsum.photos/seed/coach-ahmed/80/80" alt="Coach Ahmed">
|
||||||
</div>
|
</div>
|
||||||
Coach Ahmed • Hands drop after scoring, drill guard recovery immediately.
|
<div class="event-meta-text">
|
||||||
|
<div class="coach-name">Coach Ahmed</div>
|
||||||
|
<div class="review-note">Hands drop after scoring, drill guard recovery
|
||||||
|
immediately.</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@auth
|
@auth
|
||||||
@ -2322,15 +2438,21 @@
|
|||||||
@endauth
|
@endauth
|
||||||
</div>
|
</div>
|
||||||
<div class="event-item" data-time-start="125" data-time-end="140" data-id="rev2">
|
<div class="event-item" data-time-start="125" data-time-end="140" data-id="rev2">
|
||||||
<div
|
<div class="event-time-container">
|
||||||
style="display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
<div class="emoji">🤔</div>
|
||||||
<div style="font-size: 1.2rem; margin-bottom: 2px">🤔</div>
|
|
||||||
<div class="event-time">@02:05–02:20</div>
|
<div class="event-time">@02:05–02:20</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="event-label">Missed counter opportunity</div>
|
<div class="event-label">🤔 Missed counter opportunity</div>
|
||||||
<div class="event-meta">
|
<div class="event-meta">
|
||||||
Coach Sara • Great angle, but no follow up. Use this clip to discuss risk vs reward.
|
<div class="comment-avatar">
|
||||||
|
<img src="https://picsum.photos/seed/coach-sara/80/80" alt="Coach Sara">
|
||||||
|
</div>
|
||||||
|
<div class="event-meta-text">
|
||||||
|
<div class="coach-name">Coach Sara</div>
|
||||||
|
<div class="review-note">Great angle, but no follow up. Use this clip to
|
||||||
|
discuss risk vs reward.</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@auth
|
@auth
|
||||||
@ -2345,21 +2467,21 @@
|
|||||||
@endauth
|
@endauth
|
||||||
</div>
|
</div>
|
||||||
<div class="event-item" data-time-start="205" data-id="rev3">
|
<div class="event-item" data-time-start="205" data-id="rev3">
|
||||||
<div
|
<div class="event-time-container">
|
||||||
style="display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
<div class="emoji">😄</div>
|
||||||
<div style="font-size: 1.2rem; margin-bottom: 2px">😄</div>
|
|
||||||
<div class="event-time">@03:25</div>
|
<div class="event-time">@03:25</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="event-label">
|
<div class="event-label">😄 Excellent angle change and follow-up</div>
|
||||||
Excellent angle change and follow-up
|
|
||||||
</div>
|
|
||||||
<div class="event-meta">
|
<div class="event-meta">
|
||||||
<div class="comment-avatar">
|
<div class="comment-avatar">
|
||||||
<img src="https://picsum.photos/seed/coach-ahmed/80/80" alt="Coach Ahmed">
|
<img src="https://picsum.photos/seed/coach-ahmed/80/80" alt="Coach Ahmed">
|
||||||
</div>
|
</div>
|
||||||
Coach Ahmed • Save as positive highlight, ideal example of exit and re-entry after
|
<div class="event-meta-text">
|
||||||
scoring.
|
<div class="coach-name">Coach Ahmed</div>
|
||||||
|
<div class="review-note">Save as positive highlight, ideal example of exit and
|
||||||
|
re-entry after scoring.</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@auth
|
@auth
|
||||||
@ -2693,111 +2815,38 @@
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(`/videos/${videoId}/match-data`);
|
const response = await fetch(`/videos/${videoId}/match-data`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.success && data.rounds && data.rounds.length > 0) {
|
if (data.success) {
|
||||||
renderMatchData(data.rounds, data.reviews);
|
renderMatchData(data.rounds || [], data.reviews || []);
|
||||||
} else {
|
} else {
|
||||||
|
console.warn('No match data:', data.message);
|
||||||
renderStaticData();
|
renderStaticData();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
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();
|
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() {
|
function renderStaticData() {
|
||||||
const staticRounds = [{
|
// No static demo data - empty state
|
||||||
id: 1,
|
const pointsContainer = document.getElementById('officialEvents');
|
||||||
round_number: 1,
|
const reviewsContainer = document.getElementById('reviewEvents');
|
||||||
name: 'ROUND 1',
|
if (pointsContainer) pointsContainer.innerHTML =
|
||||||
points: [{
|
'<div style="text-align: center; padding: 40px; color: var(--text-secondary);">No match data yet. <button class="btn-add" onclick="openAddRoundModal()">+ Add Round</button></div>';
|
||||||
id: 1,
|
if (reviewsContainer) reviewsContainer.innerHTML =
|
||||||
timestamp_seconds: 20,
|
'<div style="text-align: center; padding: 40px; color: var(--text-secondary);">No coach notes yet. <button class="btn-add" onclick="openAddReviewModal()">+ Add Note</button></div>';
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMatchData(rounds, reviews) {
|
function renderMatchData(rounds, reviews) {
|
||||||
@ -2923,11 +2972,14 @@
|
|||||||
document.querySelectorAll('.event-item').forEach(item => {
|
document.querySelectorAll('.event-item').forEach(item => {
|
||||||
item.addEventListener('click', function(e) {
|
item.addEventListener('click', function(e) {
|
||||||
if (e.target.closest('.event-actions')) return;
|
if (e.target.closest('.event-actions')) return;
|
||||||
const timeStart = this.getAttribute('data-time-start');
|
const timeStart = parseFloat(this.getAttribute('data-time-start'));
|
||||||
if (timeStart) {
|
const timeEnd = parseFloat(this.getAttribute('data-time-end') || 0);
|
||||||
|
|
||||||
|
if (timeStart || timeStart === 0) {
|
||||||
const videoPlayer = document.getElementById('videoPlayer');
|
const videoPlayer = document.getElementById('videoPlayer');
|
||||||
if (videoPlayer) {
|
if (videoPlayer) {
|
||||||
videoPlayer.currentTime = parseInt(timeStart);
|
// Start 1 second early for context
|
||||||
|
videoPlayer.currentTime = Math.max(0, timeStart - 1.0);
|
||||||
videoPlayer.play();
|
videoPlayer.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user