diff --git a/app/Http/Controllers/RegistrationController.php b/app/Http/Controllers/RegistrationController.php index b7d5a38..8e486a4 100644 --- a/app/Http/Controllers/RegistrationController.php +++ b/app/Http/Controllers/RegistrationController.php @@ -310,6 +310,42 @@ class RegistrationController extends Controller ]); } + public function correctShot(Request $request) + { + $request->validate([ + 'session_id' => 'required|exists:game_sessions,id', + 'shot_number' => 'required|integer|in:1,2,3', + 'result' => 'required|boolean', + ]); + + $session = GameSession::with('registration')->findOrFail($request->session_id); + + $shot = $session->shots()->where('shot_number', $request->shot_number)->first(); + + if ($shot) { + $shot->update(['result' => $request->result]); + } else { + GameShot::create([ + 'game_session_id' => $session->id, + 'shot_number' => $request->shot_number, + 'result' => $request->result, + ]); + } + + $sessionScore = $session->calculateScore(); + $session->update(['score' => $sessionScore]); + + $registration = $session->registration; + $registration->update([ + 'total_score' => $registration->sessions()->sum('score') + ]); + + return response()->json([ + 'status' => 'ok', + 'total_score' => $registration->fresh()->total_score, + ]); + } + //Leaderboard public function leaderboard() { diff --git a/resources/views/dashboard/admin.blade.php b/resources/views/dashboard/admin.blade.php index f8e7a7b..bcdd261 100644 --- a/resources/views/dashboard/admin.blade.php +++ b/resources/views/dashboard/admin.blade.php @@ -7,7 +7,7 @@

Student Registrations

-

View registrations and manage student comments.

+

Track registrations and goal scores for today's session.

@@ -17,7 +17,7 @@ + class="text-sm text-slate-700 placeholder-slate-400 outline-none bg-transparent w-48"> @if(!empty($search)) @@ -26,14 +26,14 @@
+ class="flex items-center gap-2 bg-white hover:bg-slate-50 border border-slate-200 text-slate-700 text-sm font-medium px-4 py-2 rounded-lg transition-colors shadow-sm"> All Registrations + class="flex items-center gap-2 bg-white hover:bg-slate-50 border border-slate-200 text-slate-700 text-sm font-medium px-4 py-2 rounded-lg transition-colors shadow-sm"> @@ -60,7 +60,7 @@

{{ $total }}

-

Total Registrations

+

Total Students

@@ -95,7 +95,7 @@ - Today's Sessions + All Registrations
{{ $total }} students @@ -106,7 +106,6 @@ Name Phone Email - Country Today's Shots Total Score Actions @@ -134,19 +133,6 @@ {{ $reg['phone'] }} {{ $reg['email'] }} - - -
@if(is_null($reg['today_goals'])) @@ -280,6 +266,7 @@
@@ -596,6 +593,117 @@ function recordShot(result) { .fail(() => alert('Failed to record shot. Please try again.')); } +// ── Phone: allow only digits, +, -, spaces ─────────────────── +document.getElementById('phone').addEventListener('input', function () { + this.value = this.value.replace(/[^0-9+\-\s]/g, ''); +}); +document.getElementById('phone').addEventListener('keydown', function (e) { + const allowed = ['Backspace','Delete','ArrowLeft','ArrowRight','Tab','Home','End']; + if (allowed.includes(e.key)) return; + if (!/^[0-9+\-\s]$/.test(e.key)) e.preventDefault(); +}); + +// ── Shot correction — click an already-recorded indicator ──── +for (let i = 1; i <= 3; i++) { + document.getElementById('shotIndicator' + i).addEventListener('click', function () { + const shotIndex = i - 1; + // Only allow editing shots that have been recorded + if (shotResults[shotIndex] === undefined) return; + enterCorrectionMode(shotIndex); + }); +} + +document.getElementById('correctShotBtn').addEventListener('click', function () { + // Default to correcting the last shot + enterCorrectionMode(shotResults.length - 1); +}); + +function enterCorrectionMode(shotIndex) { + const shotNum = shotIndex + 1; + + // Show ready state with correction context + document.getElementById('shotReadyState').classList.remove('hidden'); + document.getElementById('shotDoneState').classList.add('hidden'); + document.getElementById('currentShotLabel').textContent = 'Correct Shot ' + shotNum + '?'; + document.getElementById('currentShotLabel').classList.add('text-amber-600'); + + // Highlight the indicator being corrected + for (let j = 1; j <= 3; j++) { + const el = document.getElementById('shotIndicator' + j); + if (j === shotNum) { + el.className = 'flex-1 h-10 rounded-lg border-2 border-amber-400 bg-amber-50 flex items-center justify-center text-xs font-bold text-amber-600 animate-pulse transition-all'; + } + } + + // Override goal/miss to correct this specific shot + const goalBtn = document.getElementById('goalBtn'); + const missBtn = document.getElementById('missBtn'); + + // Remove old listeners by cloning + const newGoalBtn = goalBtn.cloneNode(true); + const newMissBtn = missBtn.cloneNode(true); + goalBtn.parentNode.replaceChild(newGoalBtn, goalBtn); + missBtn.parentNode.replaceChild(newMissBtn, missBtn); + + newGoalBtn.addEventListener('click', () => correctShot(shotIndex, true)); + newMissBtn.addEventListener('click', () => correctShot(shotIndex, false)); +} + +function correctShot(shotIndex, result) { + if (!selectedSessionId) return; + const shotNumber = shotIndex + 1; + + $.ajax({ + url: '{{ route("registrations.correct-shot") }}', + method: 'POST', + data: { + _token: '{{ csrf_token() }}', + session_id: selectedSessionId, + shot_number: shotNumber, + result: result ? 1 : 0, + }, + success: function (res) { + // Update local state + shotResults[shotIndex] = result; + + // Reset button listeners back to normal + const goalBtn = document.getElementById('goalBtn'); + const missBtn = document.getElementById('missBtn'); + const newGoalBtn = goalBtn.cloneNode(true); + const newMissBtn = missBtn.cloneNode(true); + goalBtn.parentNode.replaceChild(newGoalBtn, goalBtn); + missBtn.parentNode.replaceChild(newMissBtn, missBtn); + newGoalBtn.addEventListener('click', () => recordShot(true)); + newMissBtn.addEventListener('click', () => recordShot(false)); + + document.getElementById('currentShotLabel').classList.remove('text-amber-600'); + currentShot = shotResults.length + 1; + refreshPanel(); + + // Update scores + document.getElementById('panelTotalScore').textContent = res.total_score; + if (selectedRow) { + selectedRow.dataset.totalScore = res.total_score; + selectedRow.dataset.shots = JSON.stringify(shotResults); + const scoreCell = selectedRow.querySelector('.score-cell'); + if (scoreCell) scoreCell.textContent = res.total_score; + const shotsCell = selectedRow.querySelector('.shots-cell'); + if (shotsCell) { + shotsCell.innerHTML = shotResults.map((g, i) => + g ? `
+ +
` + : `
+ +
` + ).join(''); + } + } + }, + error: function () { alert('Failed to correct shot. Please try again.'); } + }); +} + document.getElementById('goalBtn').addEventListener('click', () => recordShot(true)); document.getElementById('missBtn').addEventListener('click', () => recordShot(false)); document.getElementById('closeShotPanel').addEventListener('click', closeShotPanel); @@ -799,33 +907,4 @@ document.getElementById('submitRegistrationBtn').addEventListener('click', funct }); }); - @endpush \ No newline at end of file diff --git a/resources/views/registrations.blade.php b/resources/views/registrations.blade.php index e7115dc..36a97bd 100644 --- a/resources/views/registrations.blade.php +++ b/resources/views/registrations.blade.php @@ -34,11 +34,10 @@ -
- +
+
- @@ -76,9 +75,6 @@ data-country-flag="{{ $reg->country?->country_flag ?? '' }}" data-status="{{ $reg->status ?? 'warm' }}" data-comments="{{ json_encode($commentData) }}"> - {{-- Status TD (read-only badge) --}}
ID Name Phone Email - #{{ str_pad($reg->id, 4, '0', STR_PAD_LEFT) }} -
@@ -96,34 +92,64 @@ src="{{ $reg->country?->country_flag ?? '' }}" class="w-5 h-3.5 object-cover rounded-sm {{ $reg->country_id ? '' : 'hidden' }}" alt=""> - + data-reg-id="{{ $reg->id }}" + data-flag-target="flag-{{ $reg->id }}"> + + @foreach($countries as $country) + + @endforeach + + @else + + + {{ $reg->country?->title ?? '—' }} + + + @endif
+ @php $status = $reg->status ?? 'warm'; @endphp - - {{ match($status) { 'hot' => '🔥 Hot', 'warm' => '☀️ Warm', 'cold' => '❄️ Cold', default => '☀️ Warm' } }} - + + @if(auth()->user()->role === 'counselor') + + + + @else + + + {{ match($status) { + 'hot' => '🔥 Hot', + 'warm' => '☀️ Warm', + 'cold' => '❄️ Cold', + default => '☀️ Warm' + } }} + + + @endif + @@ -327,7 +353,7 @@ function openDetailPanel(row) { statusEl.className='text-xs px-2 py-1 rounded-full '+cls; statusEl.textContent=txt; - + document.getElementById('detailPanel').classList.remove('translate-x-full'); document.getElementById('detailPanelOverlay').classList.remove('hidden'); } @@ -433,4 +459,46 @@ document.querySelectorAll('.country-select').forEach(sel => { }); }); + @endpush \ No newline at end of file diff --git a/resources/views/scoreboard.blade.php b/resources/views/scoreboard.blade.php index 870daf7..2cbbaf8 100644 --- a/resources/views/scoreboard.blade.php +++ b/resources/views/scoreboard.blade.php @@ -110,7 +110,7 @@
Rosetta Education International
SC⚽RE & WIN
-
CHALLENGE 2025
+
CHALLENGE 2026
🏆
diff --git a/routes/web.php b/routes/web.php index 1eb4149..0c8e375 100644 --- a/routes/web.php +++ b/routes/web.php @@ -31,6 +31,7 @@ Route::middleware('auth')->group(function () { Route::post('/verify-otp', [RegistrationController::class, 'verifyOtp'])->name('verify-otp'); Route::get('/{id}/json', [RegistrationController::class, 'getRegistrationJson'])->name('get-json'); Route::post('/record-shot', [RegistrationController::class, 'recordShot'])->name('record-shot'); + Route::post('/correct-shot', [RegistrationController::class, 'correctShot'])->name('correct-shot'); Route::post('/record-shots', [RegistrationController::class, 'recordShots'])->name('record-shots'); Route::get('/{id}/history', [RegistrationController::class, 'history'])->name('history'); Route::post('/{id}/country', [RegistrationController::class, 'updateCountry'])->name('update-country');