country + status added and other changes
This commit is contained in:
@@ -7,35 +7,38 @@
|
||||
<div class="flex items-center justify-between mb-7">
|
||||
<div>
|
||||
<h1 class="text-slate-900 text-xl font-bold tracking-tight">Student Registrations</h1>
|
||||
<p class="text-slate-500 text-sm mt-0.5">Track registrations and goal scores for today's session.</p>
|
||||
<p class="text-slate-500 text-sm mt-0.5">View registrations and manage student comments.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2.5">
|
||||
<!-- Search -->
|
||||
<form method="GET" action="{{ route('home') }}" class="flex items-center">
|
||||
<div class="flex items-center gap-2 bg-white border border-slate-200 rounded-lg px-3 py-2 shadow-sm focus-within:ring-2 focus-within:ring-indigo-400 transition-all">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||||
</svg>
|
||||
<input type="text" name="search" value="{{ $search ?? '' }}" placeholder="Search name, phone, email…"
|
||||
class="text-sm text-slate-700 placeholder-slate-400 outline-none bg-transparent w-52">
|
||||
@if(!empty($search))
|
||||
<a href="{{ route('home') }}" class="text-slate-400 hover:text-slate-600 transition-colors">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
<a href="{{ route('registrations.index') }}"
|
||||
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">
|
||||
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 6h18"></path>
|
||||
<path d="M3 12h18"></path>
|
||||
<path d="M3 18h18"></path>
|
||||
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">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 6h18"/><path d="M3 12h18"/><path d="M3 18h18"/>
|
||||
</svg>
|
||||
|
||||
All Registrations
|
||||
</a>
|
||||
<a href="{{ route('leaderboard') }}"
|
||||
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">
|
||||
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">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>
|
||||
</svg>
|
||||
Leaderboard
|
||||
</a>
|
||||
<button id="newRegistrationBtn"
|
||||
class="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium px-4 py-2 rounded-lg transition-colors shadow-sm">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
|
||||
</svg>
|
||||
New Registration
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -96,6 +99,7 @@
|
||||
<th class="px-5 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Name</th>
|
||||
<th class="px-5 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Phone</th>
|
||||
<th class="px-5 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Email</th>
|
||||
<th class="px-5 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Country</th>
|
||||
<th class="px-5 py-3 text-center text-xs font-semibold text-slate-500 uppercase tracking-wider">Today's Shots</th>
|
||||
<th class="px-5 py-3 text-center text-xs font-semibold text-slate-500 uppercase tracking-wider">Total Score</th>
|
||||
<th class="px-5 py-3 text-right text-xs font-semibold text-slate-500 uppercase tracking-wider">Actions</th>
|
||||
@@ -123,6 +127,19 @@
|
||||
</td>
|
||||
<td class="px-5 py-3.5 text-slate-500">{{ $reg['phone'] }}</td>
|
||||
<td class="px-5 py-3.5 text-slate-500">{{ $reg['email'] }}</td>
|
||||
<td class="px-5 py-3.5" onclick="event.stopPropagation()">
|
||||
<select class="country-select text-xs border border-slate-200 rounded-lg px-2 py-1.5 bg-white
|
||||
text-slate-700 focus:outline-none focus:ring-2 focus:ring-indigo-400 cursor-pointer max-w-[140px]"
|
||||
data-reg-id="{{ $reg['id'] }}">
|
||||
<option value="">— Select —</option>
|
||||
@foreach($countries as $country)
|
||||
<option value="{{ $country->id }}"
|
||||
{{ ($reg['country_id'] ?? null) == $country->id ? 'selected' : '' }}>
|
||||
{{ $country->title }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</td>
|
||||
<td class="px-5 py-3.5">
|
||||
<div class="shots-cell flex items-center justify-center gap-1.5">
|
||||
@if(is_null($reg['today_goals']))
|
||||
@@ -775,4 +792,33 @@ document.getElementById('submitRegistrationBtn').addEventListener('click', funct
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.querySelectorAll('.country-select').forEach(sel => {
|
||||
sel.addEventListener('change', function () {
|
||||
const regId = this.dataset.regId;
|
||||
const countryId = this.value;
|
||||
const flagTarget = this.dataset.flagTarget;
|
||||
|
||||
if (!countryId) return;
|
||||
|
||||
// Update flag preview if present
|
||||
if (flagTarget) {
|
||||
const selectedOption = this.options[this.selectedIndex];
|
||||
const flagUrl = selectedOption.dataset.flag;
|
||||
const flagEl = document.getElementById(flagTarget);
|
||||
if (flagEl && flagUrl) {
|
||||
flagEl.src = flagUrl;
|
||||
flagEl.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '{{ route("registrations.update-country", ":id") }}'.replace(':id', regId),
|
||||
method: 'POST',
|
||||
data: { _token: '{{ csrf_token() }}', country_id: countryId },
|
||||
error: function () { alert('Failed to update country.'); }
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@@ -10,6 +10,20 @@
|
||||
<p class="text-slate-500 text-sm mt-0.5">View registrations and manage student comments.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2.5">
|
||||
<form method="GET" action="{{ route('home') }}" class="flex items-center">
|
||||
<div class="flex items-center gap-2 bg-white border border-slate-200 rounded-lg px-3 py-2 shadow-sm focus-within:ring-2 focus-within:ring-indigo-400 transition-all">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||||
</svg>
|
||||
<input type="text" name="search" value="{{ $search ?? '' }}" placeholder="Search name, phone, email…"
|
||||
class="text-sm text-slate-700 placeholder-slate-400 outline-none bg-transparent w-52">
|
||||
@if(!empty($search))
|
||||
<a href="{{ route('home') }}" class="text-slate-400 hover:text-slate-600 transition-colors">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
<a href="{{ route('registrations.index') }}"
|
||||
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">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
@@ -84,6 +98,8 @@
|
||||
<th class="px-5 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Name</th>
|
||||
<th class="px-5 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Phone</th>
|
||||
<th class="px-5 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Email</th>
|
||||
<th class="px-5 py-3 text-left text-xs font-semibold text-slate-500 uppercase tracking-wider">Country</th>
|
||||
<th class="px-5 py-3 text-center text-xs font-semibold text-slate-500 uppercase tracking-wider">Status</th>
|
||||
<th class="px-5 py-3 text-center text-xs font-semibold text-slate-500 uppercase tracking-wider">Today's Shots</th>
|
||||
<th class="px-5 py-3 text-center text-xs font-semibold text-slate-500 uppercase tracking-wider">Total Score</th>
|
||||
<th class="px-5 py-3 text-center text-xs font-semibold text-slate-500 uppercase tracking-wider">Comments</th>
|
||||
@@ -91,9 +107,16 @@
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
@foreach ($registrations as $reg)
|
||||
<tr class="hover:bg-slate-50/70 transition-colors group cursor-pointer"
|
||||
<tr class="hover:bg-slate-50 cursor-pointer"
|
||||
data-reg-id="{{ $reg['id'] }}"
|
||||
data-name="{{ $reg['name'] }}">
|
||||
data-name="{{ $reg['name'] }}"
|
||||
data-phone="{{ $reg['phone'] }}"
|
||||
data-email="{{ $reg['email'] ?? '' }}"
|
||||
data-score="{{ $reg['total_score'] }}"
|
||||
data-status="{{ $reg['status'] ?? 'warm' }}"
|
||||
data-country-title="{{ $reg['country_title'] ?? '' }}"
|
||||
data-country-flag="{{ $reg['country_flag'] ?? '' }}"
|
||||
data-shots="{{ json_encode($reg['today_goals'] ?? []) }}">
|
||||
<td class="px-5 py-3.5">
|
||||
<span class="text-xs font-mono text-slate-400">#{{ str_pad($reg['id'], 4, '0', STR_PAD_LEFT) }}</span>
|
||||
</td>
|
||||
@@ -107,6 +130,28 @@
|
||||
</td>
|
||||
<td class="px-5 py-3.5 text-slate-500">{{ $reg['phone'] }}</td>
|
||||
<td class="px-5 py-3.5 text-slate-500">{{ $reg['email'] }}</td>
|
||||
|
||||
<!-- COUNTRY -->
|
||||
<td class="px-5 py-3" onclick="event.stopPropagation()">
|
||||
<div class="flex items-center gap-2">
|
||||
@if(!empty($reg['country_flag']))
|
||||
<img src="{{ $reg['country_flag'] }}" class="w-5 h-3.5 rounded-sm">
|
||||
@endif
|
||||
<span class="text-slate-600 text-sm">{{ $reg['country_title'] ?: '—' }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- STATUS -->
|
||||
<td class="px-5 py-3 text-center" onclick="event.stopPropagation()">
|
||||
@php $status = $reg['status'] ?? 'warm'; @endphp
|
||||
<select class="status-select text-xs px-2 py-1 rounded border cursor-pointer"
|
||||
data-reg-id="{{ $reg['id'] }}">
|
||||
<option value="hot" {{ $status === 'hot' ? 'selected' : '' }}>🔥 Hot</option>
|
||||
<option value="warm" {{ $status === 'warm' ? 'selected' : '' }}>☀️ Warm</option>
|
||||
<option value="cold" {{ $status === 'cold' ? 'selected' : '' }}>❄️ Cold</option>
|
||||
</select>
|
||||
</td>
|
||||
|
||||
<td class="px-5 py-3.5">
|
||||
<div class="flex items-center justify-center gap-1.5">
|
||||
@if(is_null($reg['today_goals']))
|
||||
@@ -126,12 +171,14 @@
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="px-5 py-3.5 text-center">
|
||||
<div class="inline-flex items-center gap-1 font-bold text-slate-900">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="#6366f1" stroke="none"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||||
<span class="text-base">{{ $reg['total_score'] }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="px-5 py-3.5 text-center">
|
||||
<span class="inline-flex items-center gap-1 text-xs font-medium text-slate-500 bg-slate-100 px-2 py-1 rounded-full">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
@@ -151,20 +198,30 @@
|
||||
</div>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- COMMENT SIDE PANEL -->
|
||||
<!-- STUDENT DETAIL SIDE PANEL -->
|
||||
<!-- ============================================================ -->
|
||||
<div id="commentPanel"
|
||||
class="fixed top-0 right-0 h-full w-96 bg-white border-l border-slate-200 shadow-2xl z-40
|
||||
<div id="detailPanel"
|
||||
class="fixed top-0 right-0 h-full w-[32rem] bg-white border-l border-slate-200 shadow-2xl z-40
|
||||
transform translate-x-full transition-transform duration-300 ease-in-out
|
||||
flex flex-col">
|
||||
|
||||
<!-- Panel Header -->
|
||||
<div class="px-5 py-4 border-b border-slate-100 flex items-center justify-between bg-slate-50 shrink-0">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-slate-400 uppercase tracking-wider">Comments for</p>
|
||||
<p id="commentPanelName" class="text-slate-900 font-bold text-base mt-0.5">—</p>
|
||||
<div class="px-6 py-4 border-b border-slate-100 bg-slate-50 flex items-center justify-between shrink-0">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center shrink-0">
|
||||
<span id="panelInitial" class="text-sm font-bold text-indigo-600">—</span>
|
||||
</div>
|
||||
<div>
|
||||
<p id="panelName" class="text-slate-900 font-bold text-base leading-tight">—</p>
|
||||
<p id="panelPhone" class="text-slate-400 text-xs mt-0.5">—</p>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<img id="panelCountryFlag" class="w-5 h-3.5 hidden rounded-sm">
|
||||
<span id="panelCountryTitle" class="text-xs text-slate-500">—</span>
|
||||
<span id="panelStatus" class="text-xs px-2 py-0.5 rounded-full"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="closeCommentPanel"
|
||||
<button id="closeDetailPanel"
|
||||
class="w-7 h-7 rounded-full bg-slate-200 hover:bg-slate-300 flex items-center justify-center text-slate-500 transition-colors">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
||||
@@ -172,46 +229,70 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Comment History -->
|
||||
<div id="commentList" class="flex-1 overflow-y-auto px-5 py-4 space-y-3 min-h-0">
|
||||
<!-- filled by JS -->
|
||||
<div id="commentLoading" class="flex items-center justify-center h-full text-slate-400 text-sm">
|
||||
<svg class="animate-spin mr-2" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 11-18 0 9 9 0 0118 0"/></svg>
|
||||
Loading comments…
|
||||
</div>
|
||||
<div id="commentEmpty" class="hidden flex-col items-center justify-center h-full text-center py-12">
|
||||
<div class="w-12 h-12 rounded-full bg-slate-100 flex items-center justify-center mx-auto mb-3">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>
|
||||
</svg>
|
||||
<!-- Student Info Strip -->
|
||||
<div class="px-6 py-4 border-b border-slate-100 shrink-0">
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="bg-slate-50 rounded-xl px-3 py-3 text-center border border-slate-100">
|
||||
<p class="text-xs text-slate-400 font-medium mb-1">Total Score</p>
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="#6366f1" stroke="none"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||||
<span id="panelScore" class="text-xl font-black text-slate-900">0</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-slate-50 rounded-xl px-3 py-3 text-center border border-slate-100">
|
||||
<p class="text-xs text-slate-400 font-medium mb-1">Today's Score</p>
|
||||
<p id="panelTodayScore" class="text-xl font-black text-slate-900">—</p>
|
||||
</div>
|
||||
<div class="bg-slate-50 rounded-xl px-3 py-3 text-center border border-slate-100">
|
||||
<p class="text-xs text-slate-400 font-medium mb-1">Email</p>
|
||||
<p id="panelEmail" class="text-xs font-medium text-slate-600 truncate">—</p>
|
||||
</div>
|
||||
<p class="text-slate-500 text-sm font-medium">No comments yet</p>
|
||||
<p class="text-slate-400 text-xs mt-1">Add the first comment below.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New Comment Input -->
|
||||
<div class="px-5 py-4 border-t border-slate-100 bg-slate-50 shrink-0">
|
||||
<label class="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2">Add Comment</label>
|
||||
<textarea id="newCommentText" rows="3"
|
||||
placeholder="Write a comment about this student…"
|
||||
class="w-full border border-slate-200 rounded-xl px-3 py-2.5 text-sm text-slate-800 bg-white
|
||||
focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500
|
||||
transition-all placeholder-slate-400 resize-none"></textarea>
|
||||
<button id="submitCommentBtn"
|
||||
class="mt-2.5 w-full bg-indigo-600 hover:bg-indigo-700 active:bg-indigo-800
|
||||
text-white text-sm font-semibold py-2.5 rounded-xl transition-colors
|
||||
flex items-center justify-center gap-2">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>
|
||||
</svg>
|
||||
Post Comment
|
||||
</button>
|
||||
<!-- Scrollable content -->
|
||||
<div class="flex-1 overflow-y-auto min-h-0">
|
||||
|
||||
<!-- Today's Shots -->
|
||||
<div class="px-6 py-4 border-b border-slate-100">
|
||||
<p class="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-3">Today's Shots</p>
|
||||
<div id="panelTodayShots" class="flex items-center gap-2">
|
||||
<span class="text-xs text-slate-400 italic">Not played today.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Score History -->
|
||||
<div class="px-6 py-4 border-b border-slate-100">
|
||||
<p class="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-3">Score History</p>
|
||||
<div id="panelHistory" class="space-y-2">
|
||||
<p class="text-xs text-slate-400 italic">No sessions yet.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Comments -->
|
||||
<div class="px-6 py-4">
|
||||
<p class="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-3">Comments</p>
|
||||
<div id="panelComments" class="space-y-2"></div>
|
||||
|
||||
<div class="mt-4">
|
||||
<textarea id="panelCommentText" rows="3" placeholder="Write a comment…"
|
||||
class="w-full border border-slate-200 rounded-xl px-3 py-2.5 text-sm text-slate-800 bg-slate-50
|
||||
focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500
|
||||
transition-all placeholder-slate-400 resize-none"></textarea>
|
||||
<button id="panelCommentBtn"
|
||||
class="mt-2 w-full bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-semibold py-2.5 rounded-xl transition-colors flex items-center justify-center gap-2">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>
|
||||
</svg>
|
||||
Post Comment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Overlay -->
|
||||
<div id="commentPanelOverlay" class="fixed inset-0 bg-black/20 z-30 hidden"></div>
|
||||
<div id="detailPanelOverlay" class="fixed inset-0 bg-black/20 z-30 hidden"></div>
|
||||
|
||||
@endsection
|
||||
|
||||
@@ -220,117 +301,175 @@
|
||||
let selectedRegId = null;
|
||||
let selectedRegRow = null;
|
||||
|
||||
// ── Open panel ───────────────────────────────────────────────
|
||||
function openCommentPanel(row) {
|
||||
function openDetailPanel(row) {
|
||||
if (selectedRegRow) selectedRegRow.classList.remove('ring-2', 'ring-inset', 'ring-indigo-400', 'bg-indigo-50/40');
|
||||
selectedRegRow = row;
|
||||
row.classList.add('ring-2', 'ring-inset', 'ring-indigo-400', 'bg-indigo-50/40');
|
||||
|
||||
selectedRegId = row.dataset.regId;
|
||||
document.getElementById('commentPanelName').textContent = row.dataset.name;
|
||||
document.getElementById('newCommentText').value = '';
|
||||
selectedRegId = row.dataset.regId;
|
||||
const name = row.dataset.name;
|
||||
const phone = row.dataset.phone;
|
||||
const email = row.dataset.email;
|
||||
const score = row.dataset.score;
|
||||
const todayShots = JSON.parse(row.dataset.shots || '[]');
|
||||
const countryTitle = row.dataset.countryTitle || '';
|
||||
const countryFlag = row.dataset.countryFlag || '';
|
||||
const status = row.dataset.status || 'warm';
|
||||
|
||||
document.getElementById('commentPanel').classList.remove('translate-x-full');
|
||||
document.getElementById('commentPanelOverlay').classList.remove('hidden');
|
||||
// Header
|
||||
document.getElementById('panelInitial').textContent = name.charAt(0).toUpperCase();
|
||||
document.getElementById('panelName').textContent = name;
|
||||
document.getElementById('panelPhone').textContent = phone;
|
||||
|
||||
loadComments();
|
||||
// Info strip
|
||||
document.getElementById('panelScore').textContent = score;
|
||||
document.getElementById('panelEmail').textContent = email || '—';
|
||||
|
||||
// Country
|
||||
const flag = document.getElementById('panelCountryFlag');
|
||||
if (countryFlag && countryFlag !== 'null' && countryFlag !== '') {
|
||||
flag.src = countryFlag;
|
||||
flag.classList.remove('hidden');
|
||||
} else {
|
||||
flag.classList.add('hidden');
|
||||
}
|
||||
document.getElementById('panelCountryTitle').textContent = countryTitle || '—';
|
||||
|
||||
// Status badge
|
||||
const statusEl = document.getElementById('panelStatus');
|
||||
if (status === 'hot') {
|
||||
statusEl.className = 'text-xs px-2 py-1 rounded bg-red-50 text-red-600';
|
||||
statusEl.textContent = '🔥 Hot';
|
||||
} else if (status === 'cold') {
|
||||
statusEl.className = 'text-xs px-2 py-1 rounded bg-blue-50 text-blue-600';
|
||||
statusEl.textContent = '❄️ Cold';
|
||||
} else {
|
||||
statusEl.className = 'text-xs px-2 py-1 rounded bg-amber-50 text-amber-600';
|
||||
statusEl.textContent = '☀️ Warm';
|
||||
}
|
||||
|
||||
// Today's shots
|
||||
const shotsEl = document.getElementById('panelTodayShots');
|
||||
if (!todayShots || todayShots.length === 0) {
|
||||
shotsEl.innerHTML = '<span class="text-xs text-slate-400 italic">Not played today.</span>';
|
||||
document.getElementById('panelTodayScore').textContent = '—';
|
||||
} else {
|
||||
const todayGoals = todayShots.filter(Boolean).length;
|
||||
document.getElementById('panelTodayScore').textContent = todayGoals;
|
||||
shotsEl.innerHTML = todayShots.map((g, i) => g
|
||||
? `<div class="w-8 h-8 rounded-full bg-emerald-100 border-2 border-emerald-400 flex items-center justify-center" title="Shot ${i+1}: Goal">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#059669" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||||
</div>`
|
||||
: `<div class="w-8 h-8 rounded-full bg-slate-100 border-2 border-slate-300 flex items-center justify-center" title="Shot ${i+1}: Miss">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</div>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
// Reset
|
||||
document.getElementById('panelHistory').innerHTML = '<p class="text-xs text-slate-400 italic">Loading…</p>';
|
||||
document.getElementById('panelComments').innerHTML = '<p class="text-xs text-slate-400 italic">Loading…</p>';
|
||||
document.getElementById('panelCommentText').value = '';
|
||||
|
||||
// Load comments
|
||||
$.ajax({
|
||||
url: '{{ route("comments.index") }}',
|
||||
data: { registration_id: selectedRegId },
|
||||
success: function (r) {
|
||||
renderPanelComments((r.comments || []).map(c => ({
|
||||
comment: c.comment,
|
||||
created_at: c.created_at_human ?? c.created_at,
|
||||
})));
|
||||
}
|
||||
});
|
||||
|
||||
// Load session history
|
||||
$.get('/registrations/' + selectedRegId + '/history', function (r) {
|
||||
renderHistory(r.sessions || []);
|
||||
}).fail(function () {
|
||||
document.getElementById('panelHistory').innerHTML = '<p class="text-xs text-slate-400 italic">No sessions found.</p>';
|
||||
});
|
||||
|
||||
document.getElementById('detailPanel').classList.remove('translate-x-full');
|
||||
document.getElementById('detailPanelOverlay').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeCommentPanel() {
|
||||
document.getElementById('commentPanel').classList.add('translate-x-full');
|
||||
document.getElementById('commentPanelOverlay').classList.add('hidden');
|
||||
function renderHistory(sessions) {
|
||||
const el = document.getElementById('panelHistory');
|
||||
if (!sessions || sessions.length === 0) {
|
||||
el.innerHTML = '<p class="text-xs text-slate-400 italic">No sessions yet.</p>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = sessions.map(s => {
|
||||
const shots = [0, 1, 2].map(i => {
|
||||
if (s.shots[i] === undefined) return `<span class="text-slate-300 text-sm">⚽</span>`;
|
||||
return s.shots[i]
|
||||
? `<span class="text-sm" style="filter:drop-shadow(0 0 3px rgba(0,200,50,.6))">⚽</span>`
|
||||
: `<span class="text-red-400 text-sm font-bold">✕</span>`;
|
||||
}).join('');
|
||||
return `<div class="flex items-center justify-between bg-slate-50 border border-slate-100 rounded-xl px-3 py-2.5">
|
||||
<span class="text-xs font-medium text-slate-500">${s.date}</span>
|
||||
<div class="flex items-center gap-1">${shots}</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="#6366f1" stroke="none"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||||
<span class="text-sm font-bold text-slate-800">${s.score}</span>
|
||||
<span class="text-xs text-slate-400">pts</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderPanelComments(comments) {
|
||||
const el = document.getElementById('panelComments');
|
||||
if (!comments || comments.length === 0) {
|
||||
el.innerHTML = '<p class="text-xs text-slate-400 italic">No comments yet.</p>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = comments.map(c => `
|
||||
<div class="bg-slate-50 border border-slate-200 rounded-xl px-4 py-3">
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<span class="text-xs font-semibold text-indigo-600">Counselor</span>
|
||||
<span class="text-xs text-slate-400">${escHtml(c.created_at)}</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-700 leading-relaxed">${escHtml(c.comment)}</p>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
function closeDetailPanel() {
|
||||
document.getElementById('detailPanel').classList.add('translate-x-full');
|
||||
document.getElementById('detailPanelOverlay').classList.add('hidden');
|
||||
if (selectedRegRow) selectedRegRow.classList.remove('ring-2', 'ring-inset', 'ring-indigo-400', 'bg-indigo-50/40');
|
||||
selectedRegRow = null;
|
||||
selectedRegId = null;
|
||||
}
|
||||
|
||||
// ── Load comments ─────────────────────────────────────────────
|
||||
function loadComments() {
|
||||
const list = document.getElementById('commentList');
|
||||
const loading = document.getElementById('commentLoading');
|
||||
const empty = document.getElementById('commentEmpty');
|
||||
|
||||
// Clear previous comments (keep loading/empty nodes)
|
||||
list.querySelectorAll('.comment-item').forEach(el => el.remove());
|
||||
loading.classList.remove('hidden');
|
||||
empty.classList.add('hidden');
|
||||
|
||||
$.ajax({
|
||||
url: '{{ route("comments.index") }}',
|
||||
method: 'GET',
|
||||
data: { registration_id: selectedRegId },
|
||||
success: function (res) {
|
||||
loading.classList.add('hidden');
|
||||
|
||||
if (!res.comments || res.comments.length === 0) {
|
||||
empty.classList.remove('hidden');
|
||||
empty.classList.add('flex');
|
||||
return;
|
||||
}
|
||||
|
||||
empty.classList.add('hidden');
|
||||
res.comments.forEach(c => appendComment(c, false));
|
||||
list.scrollTop = list.scrollHeight;
|
||||
},
|
||||
error: function () {
|
||||
loading.classList.add('hidden');
|
||||
loading.textContent = 'Failed to load comments.';
|
||||
loading.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Render a single comment bubble ───────────────────────────
|
||||
function appendComment(c, scrollInto = true) {
|
||||
const list = document.getElementById('commentList');
|
||||
const el = document.createElement('div');
|
||||
el.className = 'comment-item bg-slate-50 border border-slate-200 rounded-xl px-4 py-3';
|
||||
el.innerHTML = `
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<span class="text-xs font-semibold text-indigo-600">${escHtml(c.author ?? 'Counselor')}</span>
|
||||
<span class="text-xs text-slate-400">${escHtml(c.created_at_human ?? c.created_at ?? '')}</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-700 leading-relaxed">${escHtml(c.comment)}</p>`;
|
||||
list.appendChild(el);
|
||||
if (scrollInto) list.scrollTop = list.scrollHeight;
|
||||
}
|
||||
|
||||
function escHtml(str) {
|
||||
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
// ── Submit comment ────────────────────────────────────────────
|
||||
document.getElementById('submitCommentBtn').addEventListener('click', function () {
|
||||
const text = document.getElementById('newCommentText').value.trim();
|
||||
document.getElementById('panelCommentBtn').addEventListener('click', function () {
|
||||
const text = document.getElementById('panelCommentText').value.trim();
|
||||
if (!text || !selectedRegId) return;
|
||||
|
||||
this.disabled = true;
|
||||
this.textContent = 'Posting…';
|
||||
|
||||
const btn = this;
|
||||
|
||||
$.ajax({
|
||||
url: '{{ route("comments.store") }}',
|
||||
method: 'POST',
|
||||
data: {
|
||||
_token: '{{ csrf_token() }}',
|
||||
registration_id: selectedRegId,
|
||||
comment: text,
|
||||
},
|
||||
success: function (res) {
|
||||
document.getElementById('newCommentText').value = '';
|
||||
document.getElementById('commentEmpty').classList.add('hidden');
|
||||
|
||||
if (selectedRegRow) {
|
||||
const badge = selectedRegRow.querySelector('.comment-count');
|
||||
if (badge) badge.textContent = parseInt(badge.textContent || '0') + 1;
|
||||
}
|
||||
|
||||
appendComment(res.comment);
|
||||
},
|
||||
error: function () {
|
||||
alert('Failed to post comment. Please try again.');
|
||||
data: { _token: '{{ csrf_token() }}', registration_id: selectedRegId, comment: text },
|
||||
success: function () {
|
||||
document.getElementById('panelCommentText').value = '';
|
||||
$.ajax({
|
||||
url: '{{ route("comments.index") }}',
|
||||
data: { registration_id: selectedRegId },
|
||||
success: function (r) {
|
||||
renderPanelComments((r.comments || []).map(c => ({
|
||||
comment: c.comment, created_at: c.created_at_human ?? c.created_at,
|
||||
})));
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function () { alert('Failed to post comment.'); },
|
||||
complete: function () {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg> Post Comment`;
|
||||
@@ -338,12 +477,44 @@ document.getElementById('submitCommentBtn').addEventListener('click', function (
|
||||
});
|
||||
});
|
||||
|
||||
// ── Row clicks ───────────────────────────────────────────────
|
||||
document.querySelectorAll('tr[data-reg-id]').forEach(row => {
|
||||
row.addEventListener('click', () => openCommentPanel(row));
|
||||
row.addEventListener('click', () => openDetailPanel(row));
|
||||
});
|
||||
|
||||
document.getElementById('closeCommentPanel').addEventListener('click', closeCommentPanel);
|
||||
document.getElementById('commentPanelOverlay').addEventListener('click', closeCommentPanel);
|
||||
document.getElementById('closeDetailPanel').addEventListener('click', closeDetailPanel);
|
||||
document.getElementById('detailPanelOverlay').addEventListener('click', closeDetailPanel);
|
||||
|
||||
document.querySelector('input[name="search"]').addEventListener('input', function () {
|
||||
clearTimeout(this._t);
|
||||
this._t = setTimeout(() => this.closest('form').submit(), 400);
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
const statusColors = {
|
||||
hot: 'bg-red-50 text-red-600 border-red-200',
|
||||
warm: 'bg-amber-50 text-amber-600 border-amber-200',
|
||||
cold: 'bg-blue-50 text-blue-600 border-blue-200',
|
||||
};
|
||||
|
||||
document.querySelectorAll('.status-select').forEach(sel => {
|
||||
sel.addEventListener('change', function () {
|
||||
const regId = this.dataset.regId;
|
||||
const status = this.value;
|
||||
const el = this;
|
||||
|
||||
Object.values(statusColors).forEach(c =>
|
||||
c.split(' ').forEach(cls => el.classList.remove(cls))
|
||||
);
|
||||
statusColors[status].split(' ').forEach(cls => el.classList.add(cls));
|
||||
|
||||
$.ajax({
|
||||
url: '{{ route("registrations.update-status", ":id") }}'.replace(':id', regId),
|
||||
method: 'POST',
|
||||
data: { _token: '{{ csrf_token() }}', status: status },
|
||||
error: function () { alert('Failed to update status.'); }
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
Reference in New Issue
Block a user