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:
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 = '';
|
||||
|
||||
Reference in new issue
Block a user