set ui
This commit is contained in:
parent
e474bc5315
commit
1c800d1649
|
|
@ -5,95 +5,82 @@ namespace App\Http\Controllers;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use App\Models\Notification;
|
||||
|
||||
class StudentPortalController extends Controller
|
||||
{public function index(Request $request)
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
|
||||
if (!session()->has('student_id') && !session()->has('student_log_id')) {
|
||||
return redirect('/signin')->with('error', 'Please login first!');
|
||||
}
|
||||
|
||||
|
||||
$notifications = Notification::latest()->take(10)->get();
|
||||
$unreadNotifications = Notification::where('is_read', 0)->count();
|
||||
|
||||
|
||||
return view('StudentPortal', compact('notifications', 'unreadNotifications'));
|
||||
}
|
||||
|
||||
public function studentlogin(Request $request)
|
||||
{
|
||||
|
||||
$request->validate([
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'pin' => 'required|numeric',
|
||||
]);
|
||||
{
|
||||
$request->validate([
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'pin' => 'required|numeric',
|
||||
]);
|
||||
|
||||
|
||||
$student = DB::table('student_portal_logs')
|
||||
->where('email', $request->username)
|
||||
->orWhere('studentid', $request->username)
|
||||
->first();
|
||||
$student = DB::table('student_portal_logs')
|
||||
->where('email', $request->username)
|
||||
->orWhere('studentid', $request->username)
|
||||
->first();
|
||||
|
||||
|
||||
if ($student && Hash::check($request->password, $student->password) && $student->pin == $request->pin) {
|
||||
|
||||
if ($student->status !== 'active' && $student->status !== '' && !is_null($student->status)) {
|
||||
return redirect()->back()->with('error', 'Your account is inactive!');
|
||||
}
|
||||
if ($student && Hash::check($request->password, $student->password) && $student->pin == $request->pin) {
|
||||
|
||||
// Check status strictly
|
||||
if (!empty($student->status) && $student->status !== 'active') {
|
||||
return redirect()->back()->with('error', 'Your account is inactive!');
|
||||
}
|
||||
|
||||
|
||||
$request->session()->put('student_id', $student->studentid);
|
||||
$request->session()->put('student_log_id', $student->id ?? null);
|
||||
|
||||
$name = trim(($student->first_name ?? '') . ' ' . ($student->last_name ?? ''));
|
||||
$request->session()->put('student_name', $name ?: ($student->email ?? $student->studentid));
|
||||
$request->session()->put('student_id', $student->studentid);
|
||||
$request->session()->put('student_log_id', $student->id ?? null);
|
||||
|
||||
$name = trim(($student->first_name ?? '') . ' ' . ($student->last_name ?? ''));
|
||||
$request->session()->put('student_name', $name ?: ($student->email ?? $student->studentid));
|
||||
|
||||
return redirect()->route('student.portal')->with('success', 'Logged in successfully!');
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'Invalid Email/Username, Password, or PIN!');
|
||||
return redirect()->route('student.portal')->with('success', 'Logged in successfully!');
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'Invalid Email/Username, Password, or PIN!');
|
||||
}
|
||||
|
||||
public function showProfile(Request $request)
|
||||
{
|
||||
|
||||
$log_id = $request->session()->get('student_log_id');
|
||||
$student_code = $request->session()->get('student_id');
|
||||
{
|
||||
$log_id = $request->session()->get('student_log_id');
|
||||
$student_code = $request->session()->get('student_id');
|
||||
|
||||
if (!$log_id && !$student_code) {
|
||||
return redirect('/')->with('error', 'Please login first!');
|
||||
}
|
||||
|
||||
|
||||
$student = DB::table('student_details')
|
||||
->where(function ($query) use ($log_id, $student_code) {
|
||||
if ($log_id) {
|
||||
$query->where('student_portal_log_id', $log_id);
|
||||
}
|
||||
if ($student_code) {
|
||||
|
||||
if ($log_id) {
|
||||
$query->orWhere('student_id', $student_code);
|
||||
} else {
|
||||
$query->where('student_id', $student_code);
|
||||
}
|
||||
}
|
||||
})
|
||||
->first();
|
||||
|
||||
|
||||
if (!$student) {
|
||||
return redirect()->route('student.portal')->with('error', 'Student details not found in database!');
|
||||
}
|
||||
|
||||
|
||||
return view('StudentProfile', compact('student'));
|
||||
if (!$log_id && !$student_code) {
|
||||
return redirect('/')->with('error', 'Please login first!');
|
||||
}
|
||||
|
||||
$student = DB::table('student_details')
|
||||
->where(function ($query) use ($log_id, $student_code) {
|
||||
if ($log_id) {
|
||||
$query->where('student_portal_log_id', $log_id);
|
||||
}
|
||||
if ($student_code) {
|
||||
$query->orWhere('student_id', $student_code);
|
||||
}
|
||||
})
|
||||
->first();
|
||||
|
||||
if (!$student) {
|
||||
return redirect()->route('student.portal')->with('error', 'Student details not found in database!');
|
||||
}
|
||||
|
||||
return view('StudentProfile', compact('student'));
|
||||
}
|
||||
|
||||
public function updateProfile(Request $request)
|
||||
{
|
||||
$log_id = $request->session()->get('student_log_id');
|
||||
|
|
@ -122,11 +109,7 @@ class StudentPortalController extends Controller
|
|||
$query->where('student_portal_log_id', $log_id);
|
||||
}
|
||||
if ($student_code) {
|
||||
if ($log_id) {
|
||||
$query->orWhere('student_id', $student_code);
|
||||
} else {
|
||||
$query->where('student_id', $student_code);
|
||||
}
|
||||
$query->orWhere('student_id', $student_code);
|
||||
}
|
||||
})
|
||||
->first();
|
||||
|
|
@ -148,32 +131,34 @@ class StudentPortalController extends Controller
|
|||
'updated_at' => now(),
|
||||
];
|
||||
|
||||
// 1. Check if user requested to REMOVE image
|
||||
$destinationPath = public_path('uploads/student_images');
|
||||
|
||||
// 1. Remove profile image
|
||||
if ($request->input('remove_profile_image') == '1') {
|
||||
if (!empty($student->image) && file_exists(public_path($student->image))) {
|
||||
@unlink(public_path($student->image));
|
||||
if (!empty($student->image) && File::exists(public_path($student->image))) {
|
||||
File::delete(public_path($student->image));
|
||||
}
|
||||
$updateData['image'] = null;
|
||||
}
|
||||
// 2. Check if user provided CROPPED Base64 image
|
||||
elseif (!empty($request->input('cropped_image_data'))) {
|
||||
// 2. Process Cropped Base64 image
|
||||
elseif ($request->filled('cropped_image_data')) {
|
||||
$base64Image = $request->input('cropped_image_data');
|
||||
if (preg_match('/^data:image\/(\w+);base64,/', $base64Image, $type)) {
|
||||
$data = substr($base64Image, strpos($base64Image, ',') + 1);
|
||||
$type = strtolower($type[1]);
|
||||
|
||||
if (in_array($type, ['jpg', 'jpeg', 'gif', 'png'])) {
|
||||
$data = base64_decode($data);
|
||||
if ($data !== false) {
|
||||
$filename = time() . '_' . uniqid() . '.png';
|
||||
$destinationPath = public_path('uploads/student_images');
|
||||
if (!file_exists($destinationPath)) {
|
||||
mkdir($destinationPath, 0777, true);
|
||||
$decodedData = base64_decode($data);
|
||||
if ($decodedData !== false) {
|
||||
if (!File::isDirectory($destinationPath)) {
|
||||
File::makeDirectory($destinationPath, 0755, true, true);
|
||||
}
|
||||
file_put_contents($destinationPath . '/' . $filename, $data);
|
||||
|
||||
$filename = time() . '_' . uniqid() . '.' . $type;
|
||||
File::put($destinationPath . '/' . $filename, $decodedData);
|
||||
|
||||
// Delete old image if existed
|
||||
if (!empty($student->image) && file_exists(public_path($student->image))) {
|
||||
@unlink(public_path($student->image));
|
||||
if (!empty($student->image) && File::exists(public_path($student->image))) {
|
||||
File::delete(public_path($student->image));
|
||||
}
|
||||
|
||||
$updateData['image'] = 'uploads/student_images/' . $filename;
|
||||
|
|
@ -181,18 +166,19 @@ class StudentPortalController extends Controller
|
|||
}
|
||||
}
|
||||
}
|
||||
// 3. Fallback to normal direct file upload
|
||||
// 3. Fallback to file upload
|
||||
elseif ($request->hasFile('profile_image')) {
|
||||
$file = $request->file('profile_image');
|
||||
$filename = time() . '_' . uniqid() . '.' . $file->getClientOriginalExtension();
|
||||
$destinationPath = public_path('uploads/student_images');
|
||||
if (!file_exists($destinationPath)) {
|
||||
mkdir($destinationPath, 0777, true);
|
||||
|
||||
if (!File::isDirectory($destinationPath)) {
|
||||
File::makeDirectory($destinationPath, 0755, true, true);
|
||||
}
|
||||
|
||||
$filename = time() . '_' . uniqid() . '.' . $file->getClientOriginalExtension();
|
||||
$file->move($destinationPath, $filename);
|
||||
|
||||
if (!empty($student->image) && file_exists(public_path($student->image))) {
|
||||
@unlink(public_path($student->image));
|
||||
if (!empty($student->image) && File::exists(public_path($student->image))) {
|
||||
File::delete(public_path($student->image));
|
||||
}
|
||||
|
||||
$updateData['image'] = 'uploads/student_images/' . $filename;
|
||||
|
|
@ -216,22 +202,21 @@ class StudentPortalController extends Controller
|
|||
|
||||
return redirect()->back()->with('success', 'Profile updated successfully!');
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function studentlogout(Request $request)
|
||||
{
|
||||
|
||||
$request->session()->forget(['student_id', 'student_log_id', 'student_name', 'student_email']);
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
{
|
||||
$request->session()->forget(['student_id', 'student_log_id', 'student_name', 'student_email']);
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
|
||||
if ($request->wantsJson() || $request->ajax()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Logged out successfully!'
|
||||
], 200);
|
||||
}
|
||||
|
||||
return redirect('/')->with('success', 'Logged out successfully!');
|
||||
if ($request->wantsJson() || $request->ajax()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Logged out successfully!'
|
||||
], 200);
|
||||
}
|
||||
|
||||
return redirect('/')->with('success', 'Logged out successfully!');
|
||||
}
|
||||
}
|
||||
|
|
@ -450,211 +450,191 @@
|
|||
|
||||
</div>
|
||||
|
||||
<!-- EDIT PROFILE MODAL -->
|
||||
<!-- Edit Profile Modal -->
|
||||
<!-- ========================================== -->
|
||||
<!-- 1. MAIN EDIT PROFILE MODAL -->
|
||||
<!-- ========================================== -->
|
||||
<div class="modal fade" id="editProfileModal" tabindex="-1" aria-labelledby="editProfileModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content border-0 rounded-4 shadow-lg overflow-hidden">
|
||||
<div class="modal-header text-white p-4" style="background: linear-gradient(135deg, var(--theme-navy) 0%, var(--theme-navy-dark) 100%);">
|
||||
<h5 class="modal-title fw-bold" id="editProfileModalLabel">
|
||||
<i class="fa-solid fa-user-gear me-2" style="color: var(--theme-yellow);"></i> Edit Student Profile
|
||||
<div class="modal-content text-white style-glass-modal border-0 shadow-lg" style="background: #11152B; border-radius: 20px;">
|
||||
<div class="modal-header border-bottom border-secondary border-opacity-25 px-4 py-3">
|
||||
<h5 class="modal-title fw-bold d-flex align-items-center" id="editProfileModalLabel">
|
||||
<i class="bi bi-pencil-square text-primary me-2"></i> Edit Profile Information
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('student.profile.update') }}" method="POST" enctype="multipart/form-data">
|
||||
<form action="{{ route('student.profile.update') }}" method="POST" enctype="multipart/form-data" id="editProfileForm">
|
||||
@csrf
|
||||
<!-- Hidden inputs for Base64 cropped image & Remove Request -->
|
||||
<input type="hidden" name="cropped_image_data" id="cropped_image_data">
|
||||
<input type="hidden" name="remove_profile_image" id="remove_profile_image" value="0">
|
||||
|
||||
@method('PUT')
|
||||
<div class="modal-body p-4">
|
||||
|
||||
<!-- Profile Picture Upload, Cropper & Emoji Avatar Section -->
|
||||
<div class="mb-4 text-center p-4 rounded-4 bg-light border">
|
||||
<label class="form-label fw-bold text-dark d-block mb-3">Profile Picture</label>
|
||||
|
||||
<div class="d-flex flex-column align-items-center mb-3">
|
||||
<!-- GUARANTEED SINGLE CIRCLE FRAME CONTAINER -->
|
||||
<div class="rounded-circle shadow border border-4 border-white overflow-hidden"
|
||||
style="width: 130px; height: 130px; background: var(--theme-navy); position: relative; margin: 0 auto;">
|
||||
|
||||
<img id="modal_avatar_img"
|
||||
src="{{ !empty($student->image) ? asset($student->image) : '' }}"
|
||||
alt="{{ $student->full_name }}"
|
||||
style="width: 100%; height: 100%; object-fit: cover; position: absolute; top: 0; left: 0; z-index: 2; {{ !empty($student->image) ? 'display: block;' : 'display: none;' }}"
|
||||
onerror="this.style.display='none'; document.getElementById('modal_avatar_placeholder_inner').style.display='flex';">
|
||||
|
||||
<div id="modal_avatar_placeholder_inner"
|
||||
class="w-100 h-100 d-flex align-items-center justify-content-center fw-bold position-absolute top-0 start-0"
|
||||
style="background: var(--theme-navy); color: var(--theme-yellow); font-size: 54px; z-index: 1;">
|
||||
<span id="avatar_emoji_or_initial">{{ strtoupper(substr($student->full_name ?? 'S', 0, 1)) }}</span>
|
||||
|
||||
<!-- Profile Picture Management Section -->
|
||||
<div class="card bg-dark bg-opacity-50 border border-secondary border-opacity-25 rounded-4 p-3 mb-4">
|
||||
<div class="row align-items-center g-3">
|
||||
<div class="col-auto">
|
||||
<div class="position-relative">
|
||||
<div id="modal_avatar_preview_wrapper" class="rounded-circle overflow-hidden d-flex align-items-center justify-content-center border border-2 border-primary" style="width: 90px; height: 90px; background: #1E233E;">
|
||||
@if(isset($student->image) && $student->image)
|
||||
<img id="modal_avatar_img" src="{{ asset($student->image) }}" alt="Profile Picture" class="w-100 h-100 object-fit-cover">
|
||||
<div id="modal_avatar_placeholder_inner" class="d-flex align-items-center justify-content-center w-100 h-100" style="display: none !important;">
|
||||
<span id="avatar_emoji_or_initial" class="fs-2 fw-bold text-white">{{ strtoupper(substr($student->full_name ?? 'S', 0, 1)) }}</span>
|
||||
</div>
|
||||
@else
|
||||
<img id="modal_avatar_img" src="" alt="Profile Picture" class="w-100 h-100 object-fit-cover" style="display: none;">
|
||||
<div id="modal_avatar_placeholder_inner" class="d-flex align-items-center justify-content-center w-100 h-100">
|
||||
<span id="avatar_emoji_or_initial" class="fs-2 fw-bold text-white">{{ strtoupper(substr($student->full_name ?? 'S', 0, 1)) }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="remove_photo_status" class="badge bg-danger bg-opacity-10 text-danger mt-2 py-1.5 px-3 rounded-pill d-none">
|
||||
<i class="fa-solid fa-info-circle me-1"></i> Photo will be removed upon saving.
|
||||
<div class="col">
|
||||
<h6 class="fw-bold mb-1 text-white">Profile Picture</h6>
|
||||
<p class="text-white-50 small mb-2">Upload a photo, create an emoji avatar, or remove picture.</p>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<!-- Upload Photo Button -->
|
||||
<label class="btn btn-sm btn-outline-light rounded-pill px-3 py-1 cursor-pointer">
|
||||
<i class="bi bi-camera me-1"></i> Upload Photo
|
||||
<input type="file" id="profile_image_file_input" accept="image/*" class="d-none">
|
||||
</label>
|
||||
|
||||
<!-- Emoji Picker Dropdown -->
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-sm btn-outline-info rounded-pill px-3 py-1 dropdown-toggle" type="button" id="emojiDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-emoji-smile me-1"></i> Choose Emoji
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-dark p-3 shadow-lg border-secondary" aria-labelledby="emojiDropdown" style="width: 260px; background: #1B203E;">
|
||||
<p class="small text-white-50 mb-2">Select Avatar Emoji:</p>
|
||||
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||
<button type="button" class="btn btn-dark btn-sm btn-emoji-picker fs-5" data-emoji="👨💻">👨💻</button>
|
||||
<button type="button" class="btn btn-dark btn-sm btn-emoji-picker fs-5" data-emoji="👩💻">👩💻</button>
|
||||
<button type="button" class="btn btn-dark btn-sm btn-emoji-picker fs-5" data-emoji="🎓">🎓</button>
|
||||
<button type="button" class="btn btn-dark btn-sm btn-emoji-picker fs-5" data-emoji="🚀">🚀</button>
|
||||
<button type="button" class="btn btn-dark btn-sm btn-emoji-picker fs-5" data-emoji="🔥">🔥</button>
|
||||
<button type="button" class="btn btn-dark btn-sm btn-emoji-picker fs-5" data-emoji="⚡">⚡</button>
|
||||
</div>
|
||||
<p class="small text-white-50 mb-2">Background Color:</p>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn rounded-circle p-2 btn-avatar-color border border-light" data-color="#1E233E" style="background-color: #1E233E; width: 24px; height: 24px;"></button>
|
||||
<button type="button" class="btn rounded-circle p-2 btn-avatar-color border border-light" data-color="#0d6efd" style="background-color: #0d6efd; width: 24px; height: 24px;"></button>
|
||||
<button type="button" class="btn rounded-circle p-2 btn-avatar-color border border-light" data-color="#198754" style="background-color: #198754; width: 24px; height: 24px;"></button>
|
||||
<button type="button" class="btn rounded-circle p-2 btn-avatar-color border border-light" data-color="#dc3545" style="background-color: #dc3545; width: 24px; height: 24px;"></button>
|
||||
<button type="button" class="btn rounded-circle p-2 btn-avatar-color border border-light" data-color="#6f42c1" style="background-color: #6f42c1; width: 24px; height: 24px;"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Remove Photo Button -->
|
||||
<button type="button" class="btn btn-sm btn-outline-danger rounded-pill px-3 py-1" id="btnRemovePhoto">
|
||||
<i class="bi bi-trash me-1"></i> Remove
|
||||
</button>
|
||||
</div>
|
||||
<div id="remove_photo_status" class="small text-warning mt-2 d-none">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i> Photo will be removed upon saving.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-center align-items-center gap-2 flex-wrap mb-3">
|
||||
<!-- File input button -->
|
||||
<label class="btn btn-outline-primary btn-sm rounded-pill px-3 py-2 fw-semibold cursor-pointer mb-0">
|
||||
<i class="fa-solid fa-camera me-1"></i> Choose New Photo
|
||||
<input type="file" id="profile_image_file_input" class="d-none" accept="image/*">
|
||||
</label>
|
||||
|
||||
<!-- Remove photo button -->
|
||||
<button type="button" id="btnRemovePhoto" class="btn btn-outline-danger btn-sm rounded-pill px-3 py-2 fw-semibold">
|
||||
<i class="fa-solid fa-trash-can me-1"></i> Remove Photo
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Emoji & Custom Avatar Creator Toolbar -->
|
||||
<div class="mt-3 pt-3 border-top w-100">
|
||||
<small class="fw-bold text-secondary d-block mb-2">
|
||||
<i class="fa-solid fa-icons me-1 text-primary"></i> Or Create a Fun Custom Emoji Avatar:
|
||||
</small>
|
||||
|
||||
<div class="d-flex justify-content-center gap-1.5 flex-wrap mb-2">
|
||||
<button type="button" class="btn btn-light btn-emoji-picker rounded-circle border shadow-sm p-0 d-inline-flex align-items-center justify-content-center" data-emoji="🎓" style="width: 38px; height: 38px; font-size: 1.2rem;" title="Graduate">🎓</button>
|
||||
<button type="button" class="btn btn-light btn-emoji-picker rounded-circle border shadow-sm p-0 d-inline-flex align-items-center justify-content-center" data-emoji="👨🔧" style="width: 38px; height: 38px; font-size: 1.2rem;" title="Mechanic Tech">👨🔧</button>
|
||||
<button type="button" class="btn btn-light btn-emoji-picker rounded-circle border shadow-sm p-0 d-inline-flex align-items-center justify-content-center" data-emoji="👩🔧" style="width: 38px; height: 38px; font-size: 1.2rem;" title="Female Tech">👩🔧</button>
|
||||
<button type="button" class="btn btn-light btn-emoji-picker rounded-circle border shadow-sm p-0 d-inline-flex align-items-center justify-content-center" data-emoji="🏎️" style="width: 38px; height: 38px; font-size: 1.2rem;" title="Race Car">🏎️</button>
|
||||
<button type="button" class="btn btn-light btn-emoji-picker rounded-circle border shadow-sm p-0 d-inline-flex align-items-center justify-content-center" data-emoji="👨💻" style="width: 38px; height: 38px; font-size: 1.2rem;" title="IT Student">👨💻</button>
|
||||
<button type="button" class="btn btn-light btn-emoji-picker rounded-circle border shadow-sm p-0 d-inline-flex align-items-center justify-content-center" data-emoji="⚡" style="width: 38px; height: 38px; font-size: 1.2rem;" title="Speedster">⚡</button>
|
||||
<button type="button" class="btn btn-light btn-emoji-picker rounded-circle border shadow-sm p-0 d-inline-flex align-items-center justify-content-center" data-emoji="😎" style="width: 38px; height: 38px; font-size: 1.2rem;" title="Cool Student">😎</button>
|
||||
<button type="button" class="btn btn-light btn-emoji-picker rounded-circle border shadow-sm p-0 d-inline-flex align-items-center justify-content-center" data-emoji="🚀" style="width: 38px; height: 38px; font-size: 1.2rem;" title="High Achiever">🚀</button>
|
||||
<button type="button" class="btn btn-light btn-emoji-picker rounded-circle border shadow-sm p-0 d-inline-flex align-items-center justify-content-center" data-emoji="🏆" style="width: 38px; height: 38px; font-size: 1.2rem;" title="Champion">🏆</button>
|
||||
<button type="button" class="btn btn-light btn-emoji-picker rounded-circle border shadow-sm p-0 d-inline-flex align-items-center justify-content-center" data-emoji="⭐" style="width: 38px; height: 38px; font-size: 1.2rem;" title="Star">⭐</button>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-center align-items-center gap-2 mt-2">
|
||||
<small class="text-muted" style="font-size: 0.78rem;">Background Color:</small>
|
||||
<button type="button" class="btn-avatar-color rounded-circle border border-2 border-white shadow-sm p-0" data-color="#1E233E" style="width: 26px; height: 26px; background: #1E233E;" title="Navy Blue"></button>
|
||||
<button type="button" class="btn-avatar-color rounded-circle border border-2 border-white shadow-sm p-0" data-color="#822424" style="width: 26px; height: 26px; background: #822424;" title="Accent Red"></button>
|
||||
<button type="button" class="btn-avatar-color rounded-circle border border-2 border-white shadow-sm p-0" data-color="#064E3B" style="width: 26px; height: 26px; background: #064E3B;" title="Emerald Green"></button>
|
||||
<button type="button" class="btn-avatar-color rounded-circle border border-2 border-white shadow-sm p-0" data-color="#4F46E5" style="width: 26px; height: 26px; background: #4F46E5;" title="Indigo"></button>
|
||||
<button type="button" class="btn-avatar-color rounded-circle border border-2 border-white shadow-sm p-0" data-color="#0F172A" style="width: 26px; height: 26px; background: #0F172A;" title="Dark Charcoal"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold mb-3 pb-2 border-bottom" style="color: var(--theme-navy);">
|
||||
<i class="fa-solid fa-id-card text-danger me-2"></i> Personal Details
|
||||
</h6>
|
||||
<!-- Hidden Inputs for Profile Image Handling -->
|
||||
<input type="hidden" name="cropped_image_data" id="cropped_image_data">
|
||||
<input type="hidden" name="remove_profile_image" id="remove_profile_image" value="0">
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<!-- Personal Information Form Inputs -->
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="full_name" class="form-label fw-semibold small text-secondary">Full Name <span class="text-danger">*</span></label>
|
||||
<input type="text" name="full_name" id="full_name" class="form-control rounded-3" value="{{ old('full_name', $student->full_name) }}" required>
|
||||
<label for="full_name" class="form-label text-white-50 small mb-1">Full Name</label>
|
||||
<input type="text" class="form-control text-white border-secondary border-opacity-50" id="full_name" name="full_name" value="{{ old('full_name', $student->full_name ?? '') }}" style="background: #1B203E;" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="email" class="form-label fw-semibold small text-secondary">Email Address <span class="text-danger">*</span></label>
|
||||
<input type="email" name="email" id="email" class="form-control rounded-3" value="{{ old('email', $student->email) }}" required>
|
||||
<label for="email" class="form-label text-white-50 small mb-1">Email Address</label>
|
||||
<input type="email" class="form-control text-white border-secondary border-opacity-50" id="email" name="email" value="{{ old('email', $student->email ?? '') }}" style="background: #1B203E;" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="phone" class="form-label fw-semibold small text-secondary">Phone Number</label>
|
||||
<input type="text" name="phone" id="phone" class="form-control rounded-3" value="{{ old('phone', $student->phone) }}">
|
||||
<label for="phone" class="form-label text-white-50 small mb-1">Phone Number</label>
|
||||
<input type="text" class="form-control text-white border-secondary border-opacity-50" id="phone" name="phone" value="{{ old('phone', $student->phone ?? '') }}" style="background: #1B203E;">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="nic_passport" class="form-label fw-semibold small text-secondary">NIC / Passport Number</label>
|
||||
<input type="text" name="nic_passport" id="nic_passport" class="form-control rounded-3" value="{{ old('nic_passport', $student->nic_passport) }}">
|
||||
<label for="nic_passport" class="form-label text-white-50 small mb-1">NIC / Passport</label>
|
||||
<input type="text" class="form-control text-white border-secondary border-opacity-50" id="nic_passport" name="nic_passport" value="{{ old('nic_passport', $student->nic_passport ?? '') }}" style="background: #1B203E;">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="date_of_birth" class="form-label fw-semibold small text-secondary">Date of Birth</label>
|
||||
<input type="date" name="date_of_birth" id="date_of_birth" class="form-control rounded-3" value="{{ old('date_of_birth', $student->date_of_birth) }}">
|
||||
<label for="date_of_birth" class="form-label text-white-50 small mb-1">Date of Birth</label>
|
||||
<input type="date" class="form-control text-white border-secondary border-opacity-50" id="date_of_birth" name="date_of_birth" value="{{ old('date_of_birth', $student->date_of_birth ?? '') }}" style="background: #1B203E;">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="gender" class="form-label fw-semibold small text-secondary">Gender</label>
|
||||
<select name="gender" id="gender" class="form-select rounded-3">
|
||||
<option value="Male" {{ strtolower($student->gender ?? '') == 'male' ? 'selected' : '' }}>Male</option>
|
||||
<option value="Female" {{ strtolower($student->gender ?? '') == 'female' ? 'selected' : '' }}>Female</option>
|
||||
<option value="Other" {{ strtolower($student->gender ?? '') == 'other' ? 'selected' : '' }}>Other</option>
|
||||
<label for="gender" class="form-label text-white-50 small mb-1">Gender</label>
|
||||
<select class="form-select text-white border-secondary border-opacity-50" id="gender" name="gender" style="background: #1B203E;">
|
||||
<option value="" class="bg-dark">Select Gender</option>
|
||||
<option value="Male" {{ old('gender', $student->gender ?? '') == 'Male' ? 'selected' : '' }} class="bg-dark">Male</option>
|
||||
<option value="Female" {{ old('gender', $student->gender ?? '') == 'Female' ? 'selected' : '' }} class="bg-dark">Female</option>
|
||||
<option value="Other" {{ old('gender', $student->gender ?? '') == 'Other' ? 'selected' : '' }} class="bg-dark">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="fw-bold mb-3 pb-2 border-bottom" style="color: var(--theme-navy);">
|
||||
<i class="fa-solid fa-graduation-cap text-danger me-2"></i> Educational Background
|
||||
</h6>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label for="qualification" class="form-label fw-semibold small text-secondary">Qualification</label>
|
||||
<input type="text" name="qualification" id="qualification" class="form-control rounded-3" value="{{ old('qualification', $student->qualification) }}">
|
||||
<div class="col-md-6">
|
||||
<label for="qualification" class="form-label text-white-50 small mb-1">Highest Qualification</label>
|
||||
<input type="text" class="form-control text-white border-secondary border-opacity-50" id="qualification" name="qualification" value="{{ old('qualification', $student->qualification ?? '') }}" style="background: #1B203E;">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="institute" class="form-label fw-semibold small text-secondary">Institute</label>
|
||||
<input type="text" name="institute" id="institute" class="form-control rounded-3" value="{{ old('institute', $student->institute) }}">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="year_completed" class="form-label fw-semibold small text-secondary">Year Completed</label>
|
||||
<input type="text" name="year_completed" id="year_completed" class="form-control rounded-3" value="{{ old('year_completed', $student->year_completed) }}">
|
||||
<div class="col-md-6">
|
||||
<label for="institute" class="form-label text-white-50 small mb-1">Institute / University</label>
|
||||
<input type="text" class="form-control text-white border-secondary border-opacity-50" id="institute" name="institute" value="{{ old('institute', $student->institute ?? '') }}" style="background: #1B203E;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer bg-light p-3">
|
||||
<button type="button" class="btn btn-light rounded-3 px-4 fw-bold border" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn text-white rounded-3 px-4 fw-bold" style="background: var(--theme-red); border: none;">
|
||||
<i class="fa-solid fa-check me-2"></i> Save Changes
|
||||
</button>
|
||||
<div class="modal-footer border-top border-secondary border-opacity-25 px-4 py-3">
|
||||
<button type="button" class="btn btn-outline-light rounded-pill px-4" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary rounded-pill px-4">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IMAGE CROPPER MODAL -->
|
||||
<div class="modal fade" id="cropImageModal" data-bs-backdrop="static" tabindex="-1" aria-labelledby="cropImageModalLabel" aria-hidden="true">
|
||||
<!-- ========================================== -->
|
||||
<!-- 2. SEPARATE CROPPER MODAL (High Z-Index) -->
|
||||
<!-- ========================================== -->
|
||||
<div class="modal fade" id="cropImageModal" tabindex="-1" aria-labelledby="cropImageModalLabel" aria-hidden="true" data-bs-backdrop="static" style="z-index: 1060;">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 rounded-4 shadow-lg overflow-hidden">
|
||||
<div class="modal-header text-white p-3" style="background: linear-gradient(135deg, var(--theme-navy) 0%, var(--theme-navy-dark) 100%);">
|
||||
<h6 class="modal-title fw-bold" id="cropImageModalLabel">
|
||||
<i class="fa-solid fa-crop-simple me-2" style="color: var(--theme-yellow);"></i> Crop & Adjust Profile Picture
|
||||
</h6>
|
||||
<div class="modal-content text-white" style="background: #11152B; border-radius: 20px;">
|
||||
<div class="modal-header border-bottom border-secondary border-opacity-25">
|
||||
<h5 class="modal-title fw-bold" id="cropImageModalLabel">Crop Profile Image</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body p-3 text-center">
|
||||
<div class="img-container mb-3" style="max-height: 380px; overflow: hidden; background: #0f172a; border-radius: 12px;">
|
||||
<img id="cropper_image_target" src="" style="max-width: 100%; display: block; margin: 0 auto;">
|
||||
<div class="modal-body p-3">
|
||||
<div class="img-container overflow-hidden rounded-3" style="max-height: 380px; background: #000;">
|
||||
<img id="cropper_image_target" src="" alt="Source Image" style="max-width: 100%; display: block;">
|
||||
</div>
|
||||
|
||||
<!-- Crop Control Toolbar -->
|
||||
<div class="d-flex justify-content-center gap-2 mb-2">
|
||||
<button type="button" class="btn btn-light btn-sm rounded-circle shadow-sm" id="btnZoomIn" title="Zoom In">
|
||||
<i class="fa-solid fa-magnifying-glass-plus"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-light btn-sm rounded-circle shadow-sm" id="btnZoomOut" title="Zoom Out">
|
||||
<i class="fa-solid fa-magnifying-glass-minus"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-light btn-sm rounded-circle shadow-sm" id="btnRotateRight" title="Rotate 90°">
|
||||
<i class="fa-solid fa-rotate-right"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-light btn-sm rounded-circle shadow-sm" id="btnResetCrop" title="Reset">
|
||||
<i class="fa-solid fa-arrows-rotate"></i>
|
||||
</button>
|
||||
<div class="d-flex justify-content-center gap-2 mt-3">
|
||||
<button type="button" class="btn btn-sm btn-dark border-secondary text-white" id="btnZoomIn"><i class="bi bi-zoom-in"></i> Zoom In</button>
|
||||
<button type="button" class="btn btn-sm btn-dark border-secondary text-white" id="btnZoomOut"><i class="bi bi-zoom-out"></i> Zoom Out</button>
|
||||
<button type="button" class="btn btn-sm btn-dark border-secondary text-white" id="btnRotateRight"><i class="bi bi-arrow-clockwise"></i> Rotate</button>
|
||||
<button type="button" class="btn btn-sm btn-dark border-secondary text-white" id="btnResetCrop"><i class="bi bi-arrow-counterclockwise"></i> Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer bg-light p-2.5">
|
||||
<button type="button" class="btn btn-light btn-sm rounded-3 px-3 fw-bold border" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" id="btnApplyCrop" class="btn btn-primary btn-sm rounded-3 px-4 fw-bold" style="background: var(--theme-navy); border: none; color: var(--theme-yellow);">
|
||||
<i class="fa-solid fa-check me-1"></i> Crop & Apply
|
||||
</button>
|
||||
<div class="modal-footer border-top border-secondary border-opacity-25">
|
||||
<button type="button" class="btn btn-outline-light rounded-pill px-3" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary rounded-pill px-4" id="btnApplyCrop">Crop & Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- 3. DEPENDENCIES & JAVASCRIPT LOGIC -->
|
||||
<!-- ========================================== -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let cropper = null;
|
||||
let selectedBgColor = '#1E233E';
|
||||
let currentEmoji = '🎓';
|
||||
let currentEmoji = '';
|
||||
|
||||
const fileInput = document.getElementById('profile_image_file_input');
|
||||
const cropperImgTarget = document.getElementById('cropper_image_target');
|
||||
|
|
@ -663,177 +643,149 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
|
||||
const modalAvatarImg = document.getElementById('modal_avatar_img');
|
||||
const modalAvatarPlaceholder = document.getElementById('modal_avatar_placeholder_inner');
|
||||
const avatarEmojiOrInitial = document.getElementById('avatar_emoji_or_initial');
|
||||
const removePhotoStatus = document.getElementById('remove_photo_status');
|
||||
const btnRemovePhoto = document.getElementById('btnRemovePhoto');
|
||||
|
||||
const editProfileModalEl = document.getElementById('editProfileModal');
|
||||
const cropModalEl = document.getElementById('cropImageModal');
|
||||
|
||||
let cropModal = null;
|
||||
if (cropModalEl) {
|
||||
cropModal = new bootstrap.Modal(cropModalEl, { backdrop: 'static' });
|
||||
}
|
||||
const cropImageModalEl = document.getElementById('cropImageModal');
|
||||
const cropImageModal = new bootstrap.Modal(cropImageModalEl);
|
||||
|
||||
// 1. File Selected -> Open Cropper Modal
|
||||
if (fileInput) {
|
||||
fileInput.addEventListener('change', function (e) {
|
||||
const files = e.target.files;
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
if (!file.type.match('image.*')) {
|
||||
alert('Please select an image file (JPG, PNG, GIF).');
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (evt) {
|
||||
cropperImgTarget.src = evt.target.result;
|
||||
|
||||
// Show Cropper Modal
|
||||
if (cropModal) {
|
||||
cropModal.show();
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
// Fix Backdrop Z-Index for Nested Modals
|
||||
cropImageModalEl.addEventListener('show.bs.modal', function () {
|
||||
setTimeout(() => {
|
||||
const backdrops = document.querySelectorAll('.modal-backdrop');
|
||||
if (backdrops.length > 1) {
|
||||
backdrops[backdrops.length - 1].style.zIndex = '1055';
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
|
||||
// 1. File Selection & Open Cropper Modal
|
||||
fileInput.addEventListener('change', function (e) {
|
||||
const files = e.target.files;
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function (event) {
|
||||
cropperImgTarget.src = event.target.result;
|
||||
cropImageModal.show();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Initialize Cropper on Modal Show
|
||||
cropImageModalEl.addEventListener('shown.bs.modal', function () {
|
||||
if (cropper) {
|
||||
cropper.destroy();
|
||||
}
|
||||
cropper = new Cropper(cropperImgTarget, {
|
||||
aspectRatio: 1,
|
||||
viewMode: 1,
|
||||
autoCropArea: 0.9,
|
||||
responsive: true,
|
||||
restore: false,
|
||||
guides: true,
|
||||
center: true,
|
||||
highlight: false,
|
||||
cropBoxMovable: true,
|
||||
cropBoxResizable: true,
|
||||
toggleDragModeOnDblclick: false,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Initialize Cropper when Crop Modal is fully shown
|
||||
if (cropModalEl) {
|
||||
cropModalEl.addEventListener('shown.bs.modal', function () {
|
||||
if (cropper) {
|
||||
cropper.destroy();
|
||||
}
|
||||
cropper = new Cropper(cropperImgTarget, {
|
||||
aspectRatio: 1,
|
||||
viewMode: 1,
|
||||
autoCropArea: 0.9,
|
||||
responsive: true,
|
||||
restore: false,
|
||||
checkCrossOrigin: false,
|
||||
background: false,
|
||||
});
|
||||
});
|
||||
|
||||
cropModalEl.addEventListener('hidden.bs.modal', function () {
|
||||
if (cropper) {
|
||||
cropper.destroy();
|
||||
cropper = null;
|
||||
}
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
// Toolbar Actions
|
||||
document.getElementById('btnZoomIn')?.addEventListener('click', function () {
|
||||
cropper && cropper.zoom(0.1);
|
||||
});
|
||||
|
||||
document.getElementById('btnZoomOut')?.addEventListener('click', function () {
|
||||
cropper && cropper.zoom(-0.1);
|
||||
// 3. Destroy Cropper on Modal Hide
|
||||
cropImageModalEl.addEventListener('hidden.bs.modal', function () {
|
||||
if (cropper) {
|
||||
cropper.destroy();
|
||||
cropper = null;
|
||||
}
|
||||
fileInput.value = '';
|
||||
});
|
||||
|
||||
document.getElementById('btnRotateRight')?.addEventListener('click', function () {
|
||||
cropper && cropper.rotate(90);
|
||||
});
|
||||
// 4. Cropper Controls
|
||||
document.getElementById('btnZoomIn').addEventListener('click', () => cropper && cropper.zoom(0.1));
|
||||
document.getElementById('btnZoomOut').addEventListener('click', () => cropper && cropper.zoom(-0.1));
|
||||
document.getElementById('btnRotateRight').addEventListener('click', () => cropper && cropper.rotate(90));
|
||||
document.getElementById('btnResetCrop').addEventListener('click', () => cropper && cropper.reset());
|
||||
|
||||
document.getElementById('btnResetCrop')?.addEventListener('click', function () {
|
||||
cropper && cropper.reset();
|
||||
});
|
||||
|
||||
// 3. Apply Cropped Canvas
|
||||
document.getElementById('btnApplyCrop')?.addEventListener('click', function () {
|
||||
// 5. Apply Cropped Image
|
||||
document.getElementById('btnApplyCrop').addEventListener('click', function () {
|
||||
if (!cropper) return;
|
||||
|
||||
const canvas = cropper.getCroppedCanvas({
|
||||
width: 350,
|
||||
height: 350,
|
||||
width: 300,
|
||||
height: 300,
|
||||
imageSmoothingEnabled: true,
|
||||
imageSmoothingQuality: 'high',
|
||||
});
|
||||
|
||||
if (canvas) {
|
||||
const base64Image = canvas.toDataURL('image/png');
|
||||
croppedImageDataInput.value = base64Image;
|
||||
removeProfileImageInput.value = '0';
|
||||
const base64Image = canvas.toDataURL('image/jpeg', 0.9);
|
||||
|
||||
croppedImageDataInput.value = base64Image;
|
||||
removeProfileImageInput.value = "0";
|
||||
|
||||
// Live preview update in Edit Profile Modal
|
||||
modalAvatarImg.src = base64Image;
|
||||
modalAvatarImg.style.display = 'block';
|
||||
if (removePhotoStatus) {
|
||||
removePhotoStatus.classList.add('d-none');
|
||||
}
|
||||
modalAvatarImg.src = base64Image;
|
||||
modalAvatarImg.style.display = 'block';
|
||||
if (modalAvatarPlaceholder) modalAvatarPlaceholder.style.display = 'none';
|
||||
removePhotoStatus.classList.add('d-none');
|
||||
|
||||
// Close Crop Modal
|
||||
if (cropModal) {
|
||||
cropModal.hide();
|
||||
}
|
||||
}
|
||||
cropImageModal.hide();
|
||||
});
|
||||
|
||||
// 4. Remove Photo Action
|
||||
if (btnRemovePhoto) {
|
||||
btnRemovePhoto.addEventListener('click', function () {
|
||||
removeProfileImageInput.value = '1';
|
||||
croppedImageDataInput.value = '';
|
||||
fileInput.value = '';
|
||||
// 6. Remove Photo Action
|
||||
document.getElementById('btnRemovePhoto').addEventListener('click', function () {
|
||||
croppedImageDataInput.value = '';
|
||||
removeProfileImageInput.value = "1";
|
||||
|
||||
modalAvatarImg.style.display = 'none';
|
||||
if (removePhotoStatus) {
|
||||
removePhotoStatus.classList.remove('d-none');
|
||||
}
|
||||
modalAvatarImg.style.display = 'none';
|
||||
modalAvatarImg.src = '';
|
||||
if (modalAvatarPlaceholder) modalAvatarPlaceholder.style.display = 'flex';
|
||||
removePhotoStatus.classList.remove('d-none');
|
||||
});
|
||||
|
||||
// 7. Emoji Avatar Picker
|
||||
document.querySelectorAll('.btn-emoji-picker').forEach(btn => {
|
||||
btn.addEventListener('click', function () {
|
||||
currentEmoji = this.getAttribute('data-emoji');
|
||||
generateEmojiAvatar();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 8. Avatar Background Color Picker
|
||||
document.querySelectorAll('.btn-avatar-color').forEach(btn => {
|
||||
btn.addEventListener('click', function () {
|
||||
selectedBgColor = this.getAttribute('data-color');
|
||||
generateEmojiAvatar();
|
||||
});
|
||||
});
|
||||
|
||||
// 9. Generate Canvas Based Emoji Avatar
|
||||
function generateEmojiAvatar() {
|
||||
if (!currentEmoji) return;
|
||||
|
||||
// 5. Generate Custom Emoji Canvas Avatar
|
||||
function generateEmojiAvatar(emojiSymbol, bgColor) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 300;
|
||||
canvas.height = 300;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Draw colored circle
|
||||
ctx.beginPath();
|
||||
ctx.arc(150, 150, 150, 0, Math.PI * 2);
|
||||
ctx.fillStyle = bgColor;
|
||||
ctx.fill();
|
||||
ctx.fillStyle = selectedBgColor;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Draw centered emoji
|
||||
ctx.font = '140px "Segoe UI Emoji", "Apple Color Emoji", sans-serif';
|
||||
ctx.font = '140px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(emojiSymbol, 150, 158);
|
||||
ctx.fillText(currentEmoji, canvas.width / 2, canvas.height / 2 + 10);
|
||||
|
||||
const base64Image = canvas.toDataURL('image/png');
|
||||
croppedImageDataInput.value = base64Image;
|
||||
removeProfileImageInput.value = '0';
|
||||
|
||||
// Update modal avatar preview
|
||||
croppedImageDataInput.value = base64Image;
|
||||
removeProfileImageInput.value = "0";
|
||||
|
||||
modalAvatarImg.src = base64Image;
|
||||
modalAvatarImg.style.display = 'block';
|
||||
if (removePhotoStatus) {
|
||||
removePhotoStatus.classList.add('d-none');
|
||||
}
|
||||
if (modalAvatarPlaceholder) modalAvatarPlaceholder.style.display = 'none';
|
||||
removePhotoStatus.classList.add('d-none');
|
||||
}
|
||||
|
||||
// Emoji Picker Clicks
|
||||
document.querySelectorAll('.btn-emoji-picker').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
currentEmoji = this.getAttribute('data-emoji');
|
||||
generateEmojiAvatar(currentEmoji, selectedBgColor);
|
||||
});
|
||||
});
|
||||
|
||||
// Background Color Clicks
|
||||
document.querySelectorAll('.btn-avatar-color').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
selectedBgColor = this.getAttribute('data-color');
|
||||
generateEmojiAvatar(currentEmoji, selectedBgColor);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
|
|
@ -174,6 +174,62 @@
|
|||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
/* Stat cards (Total / Active / Enrolled) */
|
||||
.stat-card {
|
||||
border: none;
|
||||
border-radius: 14px;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.08) !important;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.text-indigo {
|
||||
color: #4C51BF;
|
||||
}
|
||||
|
||||
/* Course search box */
|
||||
.course-search-wrap .form-control:focus,
|
||||
.course-search-wrap .input-group-text {
|
||||
box-shadow: none;
|
||||
border-color: #dee2e6;
|
||||
}
|
||||
|
||||
/* Table row hover */
|
||||
#coursesTable tbody tr:hover {
|
||||
background-color: rgba(45, 53, 91, 0.04);
|
||||
}
|
||||
|
||||
/* Larger, clearer image preview inside modals */
|
||||
#addCoursePreview,
|
||||
[id^="editPreview"] {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.custom-feature-check {
|
||||
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.custom-feature-check:has(input:checked) {
|
||||
background-color: rgba(45, 53, 91, 0.06);
|
||||
border-color: var(--theme-navy);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -253,11 +309,65 @@
|
|||
</div>
|
||||
@endif
|
||||
|
||||
|
||||
<!-- Summary Statistics Cards -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card stat-card shadow-sm p-3">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<span class="text-muted fw-semibold small">Total Courses</span>
|
||||
<h4 class="fw-bold mb-0 mt-1">{{ count($courses) }}</h4>
|
||||
</div>
|
||||
<div class="stat-icon bg-primary bg-opacity-10 text-primary">
|
||||
<i class="fa-solid fa-graduation-cap"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card stat-card shadow-sm p-3">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<span class="text-muted fw-semibold small">Active Courses</span>
|
||||
<h4 class="fw-bold mb-0 mt-1 text-success">
|
||||
{{ $courses->where('status', 'Active')->count() }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="stat-icon bg-success bg-opacity-10 text-success">
|
||||
<i class="fa-solid fa-book-open"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card stat-card shadow-sm p-3">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<span class="text-muted fw-semibold small">Total Enrolled Students</span>
|
||||
<h4 class="fw-bold mb-0 mt-1 text-indigo">
|
||||
{{ $courses->sum(fn($c) => $c->enrollments_count ?? $c->students_count ?? 0) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="stat-icon bg-info bg-opacity-10 text-info">
|
||||
<i class="fa-solid fa-users"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Courses Table Card -->
|
||||
<div class="card border-0 shadow-sm rounded-3">
|
||||
<div class="card-body p-3 pb-0">
|
||||
<div class="input-group input-group-sm course-search-wrap" style="max-width: 320px;">
|
||||
<span class="input-group-text bg-white border-end-0"><i class="fa-solid fa-magnifying-glass text-muted"></i></span>
|
||||
<input type="text" id="courseSearchInput" class="form-control border-start-0" placeholder="Search by title or course code...">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<table class="table table-hover align-middle mb-0" id="coursesTable">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
|
|
@ -272,7 +382,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
@forelse($courses as $course)
|
||||
<tr>
|
||||
<tr class="course-row" data-search="{{ strtolower($course->course_code . ' ' . $course->title) }}">
|
||||
<td>{{ $loop->iteration }}</td>
|
||||
<td>
|
||||
@if($course->image)
|
||||
|
|
@ -328,8 +438,8 @@
|
|||
<h5 class="modal-title fw-bold text-white"><i class="fa-solid fa-pen-to-square me-2"></i>Edit Course</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<!-- Fixed Route Name: admin.courses.update -->
|
||||
<form action="{{ route('admin.courses.update', $course->id) }}" method="POST" enctype="multipart/form-data">
|
||||
<!-- Route: admin.courses.update (POST) -->
|
||||
<form action="{{ route('admin.courses.update', $course->id) }}" method="POST" enctype="multipart/form-data" class="edit-course-form">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
|
|
@ -363,17 +473,18 @@
|
|||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Course Image</label>
|
||||
<input type="file" name="image" class="form-control" accept="image/*">
|
||||
@if($course->image)
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">Current Image:</small><br>
|
||||
<img src="{{ asset('storage/' . $course->image) }}" class="course-img-preview border mt-1" alt="Current Image">
|
||||
</div>
|
||||
@endif
|
||||
<input type="file" name="image" class="form-control image-input" accept="image/*" data-preview-target="editPreview{{ $course->id }}">
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">{{ $course->image ? 'Current Image:' : 'No image set' }}</small><br>
|
||||
<img id="editPreview{{ $course->id }}"
|
||||
src="{{ $course->image ? asset('storage/' . $course->image) : '' }}"
|
||||
class="course-img-preview border mt-1 {{ $course->image ? '' : 'd-none' }}"
|
||||
alt="Course image preview">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Description</label>
|
||||
<textarea name="desc" class="form-control" rows="3">{{ $course->desc ?? '' }}</textarea>
|
||||
<textarea name="description" class="form-control" rows="3">{{ $course->description ?? '' }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -434,7 +545,7 @@
|
|||
<i class="fa-solid fa-pen"></i>
|
||||
</button>
|
||||
|
||||
<!-- Fixed Route Name: modules.delete -->
|
||||
<!-- Route: modules.delete -->
|
||||
<form action="{{ route('modules.delete', $module->id) }}" method="POST" onsubmit="return confirm('Delete this module?')" class="d-inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
|
|
@ -447,7 +558,7 @@
|
|||
|
||||
<!-- Edit Module Collapse Form -->
|
||||
<div class="collapse mt-2 p-3 bg-white border rounded" id="editModuleCollapse{{ $module->id }}">
|
||||
<!-- Fixed Route Name: modules.update -->
|
||||
<!-- Route: modules.update (POST) -->
|
||||
<form action="{{ route('modules.update', $module->id) }}" method="POST">
|
||||
@csrf
|
||||
<h6 class="fw-bold text-warning mb-3"><i class="fa-solid fa-pen-to-square me-1"></i>Edit Module</h6>
|
||||
|
|
@ -500,7 +611,7 @@
|
|||
<i class="fa-solid fa-plus-circle me-2 text-success"></i>Add New Module
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Fixed Route Name: modules.store -->
|
||||
<!-- Route: modules.store -->
|
||||
<form action="{{ route('modules.store') }}" method="POST">
|
||||
@csrf
|
||||
<input type="hidden" name="course_id" value="{{ $course->id }}">
|
||||
|
|
@ -580,7 +691,7 @@
|
|||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
|
||||
<!-- Route name: assign.course -->
|
||||
<!-- Route: assign.course -->
|
||||
<form action="{{ route('assign.course') }}" method="POST">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
|
|
@ -619,65 +730,173 @@
|
|||
|
||||
<!-- ADD NEW COURSE MODAL -->
|
||||
<div class="modal fade" id="addCourseModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-white" style="background-color: var(--theme-navy);">
|
||||
<h5 class="modal-title fw-bold text-white"><i class="fa-solid fa-plus-circle me-2"></i>Add New Course</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<!-- Fixed Route Name: admin.courses.store -->
|
||||
<form action="{{ route('admin.courses.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Course Code</label>
|
||||
<input type="text" name="course_code" class="form-control" placeholder="e.g. DSE101" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Course Title</label>
|
||||
<input type="text" name="title" class="form-control" placeholder="e.g. Diploma in Software Engineering" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Duration</label>
|
||||
<input type="text" name="duration" class="form-control" placeholder="e.g. 1 Year / 6 Months" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Level</label>
|
||||
<select name="level" class="form-select" required>
|
||||
<option value="" selected disabled>Select Level</option>
|
||||
<option value="Certificate">Certificate</option>
|
||||
<option value="Diploma">Diploma</option>
|
||||
<option value="Advanced Diploma">Advanced Diploma</option>
|
||||
<option value="Degree">Degree</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Status</label>
|
||||
<select name="status" class="form-select" required>
|
||||
<option value="Active" selected>Active</option>
|
||||
<option value="Inactive">Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Course Image</label>
|
||||
<input type="file" name="image" class="form-control" accept="image/*">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Description</label>
|
||||
<textarea name="description" class="form-control" rows="3" placeholder="Brief course description..."></textarea>
|
||||
</div>
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4 overflow-hidden">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="modal-header border-0 text-white px-4 py-3" style="background-color: var(--theme-navy, #0f172a);">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-white bg-opacity-10 p-2 me-3 d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
|
||||
<i class="fa-solid fa-graduation-cap text-white fs-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="modal-title fw-bold text-white mb-0">Create New Course</h5>
|
||||
<small class="text-white-50">Set up course details, pricing, and features</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer bg-light">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Save Course</button>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<!-- Route: admin.courses.store -->
|
||||
<form action="{{ route('admin.courses.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="modal-body p-4 bg-light">
|
||||
<div class="row g-4">
|
||||
|
||||
<!-- Basic Information Section -->
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm rounded-3">
|
||||
<div class="card-body p-3">
|
||||
<h6 class="fw-bold text-uppercase text-secondary fs-7 mb-3">
|
||||
<i class="fa-solid fa-circle-info me-2 text-primary"></i>Basic Information
|
||||
</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold text-dark small">Course Code <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light"><i class="fa-solid fa-hashtag text-muted"></i></span>
|
||||
<input type="text" name="course_code" class="form-control" placeholder="e.g. DSE101" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<label class="form-label fw-semibold text-dark small">Course Title <span class="text-danger">*</span></label>
|
||||
<input type="text" name="title" class="form-control" placeholder="e.g. Diploma in Software Engineering" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold text-dark small">Duration <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light"><i class="fa-regular fa-clock text-muted"></i></span>
|
||||
<input type="text" name="duration" class="form-control" placeholder="e.g. 1 Year / 6 Months" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold text-dark small">Academic Level <span class="text-danger">*</span></label>
|
||||
<select name="level" class="form-select" required>
|
||||
<option value="" selected disabled>Select Level</option>
|
||||
<option value="Certificate">Certificate</option>
|
||||
<option value="Diploma">Diploma</option>
|
||||
<option value="Advanced Diploma">Advanced Diploma</option>
|
||||
<option value="Degree">Degree</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Course Features Section -->
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm rounded-3">
|
||||
<div class="card-body p-3">
|
||||
<h6 class="fw-bold text-uppercase text-secondary fs-7 mb-3">
|
||||
<i class="fa-solid fa-list-check me-2 text-primary"></i>Key Features & Highlights
|
||||
</h6>
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="form-check custom-feature-check border rounded p-2 d-flex align-items-center">
|
||||
<input class="form-check-input ms-1 me-2" type="checkbox" name="features[]" value="Certificate Included" id="featCert">
|
||||
<label class="form-check-label w-100 small fw-medium" for="featCert">
|
||||
<i class="fa-solid fa-award text-warning me-2"></i>Certificate of Completion
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-check custom-feature-check border rounded p-2 d-flex align-items-center">
|
||||
<input class="form-check-input ms-1 me-2" type="checkbox" name="features[]" value="Live Sessions" id="featLive">
|
||||
<label class="form-check-label w-100 small fw-medium" for="featLive">
|
||||
<i class="fa-solid fa-video text-danger me-2"></i>Live Interactive Classes
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-check custom-feature-check border rounded p-2 d-flex align-items-center">
|
||||
<input class="form-check-input ms-1 me-2" type="checkbox" name="features[]" value="Projects" id="featProjects">
|
||||
<label class="form-check-label w-100 small fw-medium" for="featProjects">
|
||||
<i class="fa-solid fa-diagram-project text-success me-2"></i>Hands-on Real Projects
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-check custom-feature-check border rounded p-2 d-flex align-items-center">
|
||||
<input class="form-check-input ms-1 me-2" type="checkbox" name="features[]" value="Lifetime Access" id="featAccess">
|
||||
<label class="form-check-label w-100 small fw-medium" for="featAccess">
|
||||
<i class="fa-solid fa-infinity text-info me-2"></i>Lifetime Content Access
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom Tags / Highlights Input -->
|
||||
<label class="form-label fw-semibold text-dark small">Additional Highlights</label>
|
||||
<input type="text" name="custom_features" class="form-control form-control-sm" placeholder="e.g. 1-on-1 Mentorship, Job Placement Assistance (separated by commas)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Media & Status Section -->
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm rounded-3">
|
||||
<div class="card-body p-3">
|
||||
<h6 class="fw-bold text-uppercase text-secondary fs-7 mb-3">
|
||||
<i class="fa-solid fa-gear me-2 text-primary"></i>Media & Visibility
|
||||
</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold text-dark small">Course Banner Image</label>
|
||||
<input type="file" name="image" class="form-control image-input" accept="image/*" data-preview-target="addCoursePreview">
|
||||
<div class="form-text fs-8">PNG, JPG, or WEBP up to 2MB.</div>
|
||||
<img id="addCoursePreview" src="" class="course-img-preview border mt-2 d-none" alt="New course image preview">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold text-dark small">Publishing Status <span class="text-danger">*</span></label>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="radio" class="btn-check" name="status" id="statusActive" value="Active" checked>
|
||||
<label class="btn btn-outline-success w-50 py-2 d-flex align-items-center justify-content-center gap-2" for="statusActive">
|
||||
<i class="fa-solid fa-circle-check"></i> Active
|
||||
</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="status" id="statusInactive" value="Inactive">
|
||||
<label class="btn btn-outline-secondary w-50 py-2 d-flex align-items-center justify-content-center gap-2" for="statusInactive">
|
||||
<i class="fa-solid fa-circle-pause"></i> Draft
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold text-dark small">Description</label>
|
||||
<textarea name="description" class="form-control" rows="3" placeholder="Write a summary of what students will learn..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="modal-footer border-0 px-4 py-3 bg-white">
|
||||
<button type="button" class="btn btn-outline-secondary rounded-pill px-4" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary rounded-pill px-4 shadow-sm">
|
||||
<i class="fa-solid fa-arrow-right me-1"></i> Save Course
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Logout Form -->
|
||||
<form id="adminLogoutForm" action="{{ route('admin.logout') }}" method="POST" class="d-none">
|
||||
@csrf
|
||||
|
|
@ -706,6 +925,73 @@
|
|||
function submitAdminLogout() {
|
||||
document.getElementById('adminLogoutForm').submit();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Image preview: any <input type="file" class="image-input">
|
||||
// updates the <img> whose id matches its data-preview-target.
|
||||
// ------------------------------------------------------------
|
||||
document.querySelectorAll('.image-input').forEach(function (input) {
|
||||
input.addEventListener('change', function () {
|
||||
const targetId = input.getAttribute('data-preview-target');
|
||||
const preview = document.getElementById(targetId);
|
||||
if (!preview) return;
|
||||
|
||||
const file = input.files && input.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
preview.src = e.target.result;
|
||||
preview.classList.remove('d-none');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Live search: filters course rows by title / course code.
|
||||
// ------------------------------------------------------------
|
||||
const courseSearchInput = document.getElementById('courseSearchInput');
|
||||
if (courseSearchInput) {
|
||||
courseSearchInput.addEventListener('input', function () {
|
||||
const term = courseSearchInput.value.trim().toLowerCase();
|
||||
const rows = document.querySelectorAll('#coursesTable tbody tr.course-row');
|
||||
let visibleCount = 0;
|
||||
|
||||
rows.forEach(function (row) {
|
||||
const haystack = row.getAttribute('data-search') || '';
|
||||
const match = haystack.includes(term);
|
||||
row.classList.toggle('d-none', !match);
|
||||
if (match) visibleCount++;
|
||||
});
|
||||
|
||||
let emptyRow = document.getElementById('courseSearchEmptyRow');
|
||||
const table = document.querySelector('#coursesTable tbody');
|
||||
|
||||
if (visibleCount === 0 && term !== '') {
|
||||
if (!emptyRow) {
|
||||
emptyRow = document.createElement('tr');
|
||||
emptyRow.id = 'courseSearchEmptyRow';
|
||||
emptyRow.innerHTML = '<td colspan="8" class="text-center py-4 text-muted">No courses match "<span id="courseSearchTermLabel"></span>".</td>';
|
||||
table.appendChild(emptyRow);
|
||||
}
|
||||
emptyRow.querySelector('#courseSearchTermLabel').textContent = courseSearchInput.value.trim();
|
||||
emptyRow.classList.remove('d-none');
|
||||
} else if (emptyRow) {
|
||||
emptyRow.classList.add('d-none');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Auto-dismiss success/error alerts after a few seconds.
|
||||
// ------------------------------------------------------------
|
||||
document.querySelectorAll('.alert-dismissible').forEach(function (alertEl) {
|
||||
setTimeout(function () {
|
||||
const bsAlert = bootstrap.Alert.getOrCreateInstance(alertEl);
|
||||
bsAlert.close();
|
||||
}, 4000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -446,51 +446,39 @@
|
|||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-user-profile">
|
||||
@if(session()->has('student_id'))
|
||||
<div class="dropdown dropup">
|
||||
<a class="user-dropdown-toggle dropdown-toggle" href="#" role="button" id="userMenuDesktop" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<div class="user-avatar-badge-sm">
|
||||
{{ strtoupper(substr(session('student_name', 'S'), 0, 1)) }}
|
||||
</div>
|
||||
<div class="user-info-text">
|
||||
<span class="user-name-text">{{ session('student_name', 'Student') }}</span>
|
||||
<span class="user-email-text">Active Session</span>
|
||||
</div>
|
||||
<div class="sidebar-user-profile">
|
||||
@if(Auth::check())
|
||||
@php
|
||||
$displayName = Auth::user()->first_name . ' ' . Auth::user()->last_name;
|
||||
@endphp
|
||||
<div class="dropdown">
|
||||
<a class="dropdown-toggle" href="#" role="button" id="userMenuDesktop" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-solid fa-circle-user" style="font-size: 24px;"></i>
|
||||
<span class="user-profile-name text-truncate" style="max-width: 160px;">{{ $displayName }}</span>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu user-dropdown-menu shadow-lg p-2 animate slideIn" aria-labelledby="userMenuDesktop">
|
||||
<ul class="dropdown-menu dropdown-menu-start shadow w-100" aria-labelledby="userMenuDesktop">
|
||||
<li>
|
||||
<a class="dropdown-item user-dropdown-item py-2.5 px-3 rounded-3" href="{{ route('StudentProfile') }}">
|
||||
<div class="dropdown-item-icon-blue">
|
||||
<i class="fa-solid fa-user-gear"></i>
|
||||
</div>
|
||||
<div>
|
||||
<span class="fw-bold d-block text-white" style="font-size: 0.88rem;">My Profile</span>
|
||||
<small class="text-white-50" style="font-size: 0.72rem;">View & edit details</small>
|
||||
</div>
|
||||
<a class="dropdown-item" href="/StudentProfile">
|
||||
<i class="fa-solid fa-user me-2"></i> Profile
|
||||
</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider border-white opacity-10 my-1"></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<button type="button" onclick="submitLogout()" class="dropdown-item user-dropdown-item py-2.5 px-3 rounded-3 border-0 bg-transparent w-100 text-start">
|
||||
<div class="dropdown-item-icon-red">
|
||||
<i class="fa-solid fa-right-from-bracket"></i>
|
||||
</div>
|
||||
<div>
|
||||
<span class="fw-bold d-block text-danger" style="font-size: 0.88rem;">Logout</span>
|
||||
<small class="text-danger-subtle opacity-75" style="font-size: 0.72rem;">Sign out of session</small>
|
||||
</div>
|
||||
</button>
|
||||
<a href="#" onclick="submitLogout(); return false;" class="nav-link logout-link mt-auto" style="color:black;">
|
||||
<i class="fa-solid fa-right-from-bracket"></i>Logout
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@else
|
||||
<div class="d-flex gap-2">
|
||||
<a href="/" class="btn btn-outline-light btn-sm w-100 rounded-pill">Sign In</a>
|
||||
<div class="d-flex gap-2 px-2">
|
||||
<a href="/signin" class="btn btn-outline-light btn-sm w-50">Sign In</a>
|
||||
<a href="/signup" class="btn btn-warning btn-sm w-50">Register</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
<main class="main">
|
||||
|
|
|
|||
Loading…
Reference in New Issue