Match highlights: DB-backed rounds/points, draggable point capture, sports sheet Edit

Match highlights sidebar (videos/types/match.blade.php)
- Replaced hardcoded 3-round/5-point/3-review mock with server-rendered loops
  over $video->matchRounds (with points) and $video->coachReviews. Owner-only
  buttons carry real DB ids to the existing JS handlers.
- Fixed savePoint create branch: was doing parseInt(rawMmSs) which truncated
  "1.15" -> 1 second. Now sends the parsed value that edit already used.
- Added a point-capture bar: clicking + on a round pauses the video and shows a
  bottom bar with a big draggable red marker + nudge buttons. Dragging seeks
  the video live; Confirm reads video.currentTime and opens the Add Point modal
  with mm.ss pre-filled and Action focused. Fixed both server- and JS-rendered
  round buttons so dynamic reloads keep the new flow.

Edit → sports sheet routing
- Added sports_match_id to /videos/{video}/edit JSON.
- openEditVideoModal delegates type=match videos to openSportsMatchModal —
  edit mode when a SportsMatch is linked, create-with-attached-video mode
  when the video is match-typed but has no SportsMatch row yet.
- Sports sheet gained attachExistingVideo() so create-mode can be seeded with
  a video that's already in the library (skips the upload dropzone).

Upload chooser mobile bottom-nav
- Bottom nav "Upload" now opens the type chooser (bottom sheet on mobile,
  centred modal on desktop) instead of hard-navigating to /videos/create.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-08-08 04:01:06 +03:00
1 parent 76f5ef0c84
commit ad6cbaf742
5 files changed
+384 -229

No files matched your search

@@ -524,6 +524,72 @@
.sm-img-avatar .sm-img-ph { min-height: 74px; gap: 2px; font-size: 10px; }
.sm-img-avatar .sm-img-ph i { font-size: 16px; }
.sm-img-avatar .sm-img-preview { width: 74px; height: 74px; max-height: 74px; }
/* ── Mobile: render as a bottom sheet ─────────────────────────── */
@media (max-width: 768px) {
#sportsMatchModal .modal-dialog {
margin: 0;
max-width: 100%;
width: 100%;
min-height: 100%;
display: flex;
align-items: flex-end;
}
#sportsMatchModal .sm-content {
border-radius: 20px 20px 0 0;
border-left: none;
border-right: none;
border-bottom: none;
width: 100%;
max-height: 92vh;
display: flex;
flex-direction: column;
box-shadow: 0 -12px 40px rgba(0,0,0,.6);
transform: translateY(100%);
transition: transform .3s cubic-bezier(.22,1,.36,1);
}
#sportsMatchModal.show .sm-content { transform: translateY(0); }
/* Grabber handle at the top */
#sportsMatchModal .sm-header {
position: relative;
padding-top: 18px;
}
#sportsMatchModal .sm-header::before {
content: '';
position: absolute;
top: 6px; left: 50%;
transform: translateX(-50%);
width: 40px; height: 4px;
background: #333; border-radius: 2px;
}
#sportsMatchModal .modal-body {
overflow-y: auto;
-webkit-overflow-scrolling: touch;
flex: 1 1 auto;
padding-bottom: calc(16px + env(safe-area-inset-bottom));
}
#sportsMatchModal .sm-footer {
padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
gap: 6px;
flex-wrap: wrap;
}
#sportsMatchModal .sm-footer .btn {
flex: 1 1 auto;
font-size: 13px;
padding: 10px 12px;
}
/* Slide-up animation on the modal fade class as well */
#sportsMatchModal.fade .modal-dialog { transform: none; }
}
@media (max-width: 480px) {
#sportsMatchModal .sm-header h5 { font-size: 15px; }
#sportsMatchModal .sm-header-icon { width: 34px; height: 34px; font-size: 16px; }
}
</style>
<script>
@@ -555,12 +621,28 @@
function token() { return form.querySelector('[name="_token"]').value; }
// ── Public entry point ───────────────────────────────────────────────
window.openSportsMatchModal = function (matchId) {
// - openSportsMatchModal(matchId) → edit existing match
// - openSportsMatchModal({videoId, videoTitle}) → create against an existing video
// - openSportsMatchModal() → fresh create (upload a new video)
window.openSportsMatchModal = function (arg) {
resetForm();
if (matchId) loadMatch(matchId);
if (typeof arg === 'number' || typeof arg === 'string') {
loadMatch(arg);
} else if (arg && typeof arg === 'object' && arg.videoId) {
attachExistingVideo(arg.videoId, arg.videoTitle || '');
}
getModal().show();
};
function attachExistingVideo(videoId, videoTitle) {
videoIdInp.value = videoId;
document.getElementById('sm-video-create').classList.add('d-none');
document.getElementById('sm-video-attached').classList.remove('d-none');
document.getElementById('sm-video-attached-title').textContent = videoTitle || ('Video #' + videoId);
// Advanced fields are also useful when the video already exists.
document.getElementById('sm-advanced').classList.remove('d-none');
}
function resetForm() {
form.reset();
idInput.value = '';