set admin

This commit is contained in:
imadhigp 2026-08-13 17:08:19 +05:30
parent 97cf0f72d2
commit d6e1fe2c5d
11 changed files with 793 additions and 110 deletions

View File

@ -3,36 +3,71 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Timetable; use App\Models\User;
use App\Models\Notification; use App\Models\Notification;
use App\Models\Timetable;
use App\Models\StudentPortalLog;
use Exception;
class AdminDashboardController extends Controller class AdminDashboardController extends Controller
{ {
public function index() public function index()
{ {
if (!session()->has('admin_id')) { try {
return redirect()->route('admin.login')->with('error', 'Please login first.'); // 1. Fetch Users
} $users = User::all();
// 2. Fetch Notifications
$notifications = Notification::orderBy('created_at', 'desc')->get();
$unreadNotifications = Notification::where('is_read', false)->count();
$totalTimetableSlots = Timetable::count(); // 3. Fetch Timetable Slots Count
$totalTimetableSlots = class_exists(Timetable::class) ? Timetable::count() : 0;
$unreadNotifications = Notification::where('target_role', 'admin') // 4. Fetch Portal Logs with user eager loaded
->where('is_read', false) $portalLogs = class_exists(StudentPortalLog::class)
->count(); ? StudentPortalLog::with('user')->get()
: [];
$notifications = Notification::where('target_role', 'admin') return view('admin.AdminDashboard', compact(
->orderBy('created_at', 'desc') 'users',
->take(5) 'notifications',
->get();
return view('admin.admindashboard', compact(
'totalTimetableSlots',
'unreadNotifications', 'unreadNotifications',
'notifications' 'totalTimetableSlots',
'portalLogs'
)); ));
} catch (Exception $e) {
\Log::error('Admin Dashboard Error: ' . $e->getMessage());
if (config('app.debug')) {
throw $e;
} }
return back()->with('error', 'Something went wrong: ' . $e->getMessage());
}
}
// Notifications Store
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'message' => 'required|string',
'type' => 'required|in:info,success,warning',
]);
Notification::create([
'title' => $request->title,
'message' => $request->message,
'type' => $request->type,
'is_read' => false,
]);
return redirect()->back()->with('success', 'Notification created successfully!');
}
// Mark as Read
public function markAsRead($id) public function markAsRead($id)
{ {
$notification = Notification::findOrFail($id); $notification = Notification::findOrFail($id);
@ -40,4 +75,57 @@ class AdminDashboardController extends Controller
return redirect()->back()->with('success', 'Notification marked as read!'); return redirect()->back()->with('success', 'Notification marked as read!');
} }
// Update Notification
public function update(Request $request, $id)
{
$request->validate([
'title' => 'required|string|max:255',
'message' => 'required|string',
'type' => 'required|in:info,success,warning',
]);
$notification = Notification::findOrFail($id);
$notification->update($request->only(['title', 'message', 'type']));
return redirect()->back()->with('success', 'Notification updated successfully!');
}
// Delete Notification
public function destroy($id)
{
$notification = Notification::findOrFail($id);
$notification->delete();
return redirect()->back()->with('success', 'Notification deleted successfully!');
}
// Send User to Portal Log
public function sendToPortalLog($userId)
{
try {
$user = User::findOrFail($userId);
// Check if user is already logged
$exists = StudentPortalLog::where('user_id', $user->id)->exists();
if ($exists) {
return redirect()->back()->with('error', 'This user is already in Portal Logs!');
}
// Create new portal log
StudentPortalLog::create([
'user_id' => $user->id,
'first_name' => $user->first_name ?? '',
'last_name' => $user->last_name ?? '',
'email' => $user->email,
'phone' => $user->phone ?? null,
]);
return redirect()->back()->with('success', 'User successfully added to Portal Logs!');
} catch (Exception $e) {
\Log::error('Send to Portal Log Error: ' . $e->getMessage());
return redirect()->back()->with('error', 'Failed to add user to Portal Log.');
}
}
} }

View File

@ -5,9 +5,23 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use App\Models\Notification;
class StudentPortalController extends Controller class StudentPortalController extends Controller
{ {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) public function studentlogin(Request $request)
{ {
@ -202,4 +216,22 @@ class StudentPortalController extends Controller
return redirect()->back()->with('success', 'Profile updated successfully!'); 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();
if ($request->wantsJson() || $request->ajax()) {
return response()->json([
'success' => true,
'message' => 'Logged out successfully!'
], 200);
}
return redirect('/')->with('success', 'Logged out successfully!');
}
} }

View File

@ -18,3 +18,4 @@ class Notification extends Model
'target_role', 'target_role',
]; ];
} }

View File

@ -2,24 +2,37 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class StudentPortalLog extends Model class StudentPortalLog extends Model
{ {
use HasFactory;
protected $table = 'student_portal_logs'; protected $table = 'student_portal_logs';
protected $fillable = [ protected $fillable = [
'user_id',
'first_name',
'last_name',
'email', 'email',
'phone',
'studentid', 'studentid',
'password', 'password',
'pin', 'pin',
'status', 'status',
]; ];
protected $hidden = [ protected $hidden = [
'password', 'password',
'pin', 'pin',
]; ];
/**
* Get the user associated with this portal log.
*/
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
} }

View File

@ -11,12 +11,18 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Schema::table('student_portal_logs', function (Blueprint $table) { Schema::create('student_portal_logs', function (Blueprint $table) {
$table->foreignId('user_id') $table->id();
->nullable() $table->foreignId('user_id')->nullable()->constrained('users')->onDelete('cascade');
->after('email') $table->string('first_name')->nullable();
->constrained('users') $table->string('last_name')->nullable();
->onDelete('cascade'); $table->string('email');
$table->string('phone')->nullable();
$table->string('studentid')->nullable();
$table->string('password')->nullable();
$table->string('pin')->nullable();
$table->string('status')->default('active');
$table->timestamps();
}); });
} }
@ -25,9 +31,6 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Schema::table('student_portal_logs', function (Blueprint $table) { Schema::dropIfExists('student_portal_logs');
$table->dropForeign(['user_id']);
$table->dropColumn('user_id');
});
} }
}; };

View File

@ -321,6 +321,65 @@
</div> </div>
<div class="col-lg-5" id="notifications-section" style="width: 100%;">
<div class="card border-0 shadow-sm rounded-3 h-100">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
<h5 class="fw-bold mb-0 text-dark">
<i class="fa-solid fa-bell text-danger me-2"></i>System Notifications
</h5>
<span class="badge bg-danger rounded-pill">{{ $unreadNotifications ?? 0 }} Unread</span>
</div>
<div class="card-body p-0" style="max-height: 450px; overflow-y: auto;">
<ul class="list-group list-group-flush">
@forelse($notifications ?? [] as $notification)
<li class="list-group-item p-3 border-bottom {{ $notification->is_read ? 'bg-light text-muted' : 'bg-white' }}">
<div class="d-flex justify-content-between align-items-start">
<div class="me-2">
{{-- Type Badge --}}
@if($notification->type == 'success')
<span class="badge bg-success mb-1">Success</span>
@elseif($notification->type == 'warning')
<span class="badge bg-warning text-dark mb-1">Warning</span>
@else
<span class="badge bg-info text-dark mb-1">Info</span>
@endif
<h6 class="fw-bold mb-1 {{ $notification->is_read ? 'text-secondary' : 'text-dark' }}">
{{ $notification->title }}
</h6>
<p class="mb-1 text-muted small">{{ $notification->message }}</p>
<small class="text-secondary opacity-75 d-block" style="font-size: 11px;">
<i class="fa-regular fa-clock me-1"></i>{{ $notification->created_at->diffForHumans() }}
</small>
</div>
{{-- Mark as Read Button --}}
@if(!$notification->is_read)
<form action="{{ route('admin.notifications.read', $notification->id) }}" method="POST">
@csrf
<button type="submit" class="btn btn-sm btn-outline-success border-0 rounded-circle" title="Mark as Read">
<i class="fa-solid fa-check"></i>
</button>
</form>
@else
<span class="text-muted" title="Read"><i class="fa-solid fa-check-double text-secondary"></i></span>
@endif
</div>
</li>
@empty
<li class="list-group-item text-center py-4 text-muted">
<i class="fa-solid fa-inbox fs-3 d-block mb-2 text-secondary"></i>
No notifications found.
</li>
@endforelse
</ul>
</div>
</div>
</div>
<!-- Student Services Grid --> <!-- Student Services Grid -->
<div class="mb-4"> <div class="mb-4">
<h5 class="fw-bold text-dark mb-3 d-flex align-items-center gap-2"> <h5 class="fw-bold text-dark mb-3 d-flex align-items-center gap-2">

View File

@ -4,6 +4,9 @@
@section('content') @section('content')
<style> <style>
html {
scroll-behavior: smooth;
}
.welcome-card { .welcome-card {
background: linear-gradient(135deg, #1e293b 0%, #334155 100%); background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
color: #ffffff; color: #ffffff;
@ -26,19 +29,37 @@
justify-content: center; justify-content: center;
font-size: 24px; font-size: 24px;
} }
/* Edit Mode Control Styles */
.edit-only {
display: none !important;
}
.is-editing .edit-only {
display: inline-block !important;
}
.is-editing div.edit-only {
display: block !important;
}
.is-editing flex.edit-only,
.is-editing .d-flex.edit-only {
display: flex !important;
}
</style> </style>
<div class="container-fluid py-4"> <div class="container-fluid py-4" id="dashboardWrapper">
<!-- Welcome Banner --> <!-- Welcome Banner & Pin Unlock Header -->
<div class="welcome-card p-4 mb-4 shadow-sm d-flex justify-content-between align-items-center"> <div class="welcome-card p-4 mb-4 shadow-sm d-flex justify-content-between align-items-center">
<div> <div>
<h3 class="fw-bold mb-1"><i class="fa-solid fa-gauge me-2"></i>Welcome to Admin Dashboard</h3> <h3 class="fw-bold mb-1"><i class="fa-solid fa-gauge me-2"></i>Welcome to Admin Dashboard</h3>
<p class="mb-0 text-white-50">Automobile Engineering Academy System Overview</p> <p class="mb-0 text-white-50">Automobile Engineering Academy System Overview</p>
</div> </div>
<div class="text-end d-none d-md-block"> <!-- <div class="text-end d-flex align-items-center gap-2"> -->
<span class="badge bg-danger fs-6 px-3 py-2 rounded-pill"><i class="fa-solid fa-shield-halved me-1"></i> System Active</span> <!-- Pin Unlock Button -->
</div> <!-- <button id="btnLockToggle" class="btn btn-warning text-dark fw-bold rounded-pill px-3 py-2 shadow-sm" data-bs-toggle="modal" data-bs-target="#pinAuthModal">
<i class="fa-solid fa-lock me-1" id="lockIcon"></i> <span id="lockStatusText">Unlock Edit Mode</span>
</button>
<span class="badge bg-danger fs-6 px-3 py-2 rounded-pill d-none d-md-inline-block"><i class="fa-solid fa-shield-halved me-1"></i> System Active</span>
</div> -->
</div> </div>
<!-- Alert Messages --> <!-- Alert Messages -->
@ -49,9 +70,15 @@
</div> </div>
@endif @endif
@if(session('error'))
<div class="alert alert-danger alert-dismissible fade show mb-4" role="alert">
<i class="fa-solid fa-triangle-exclamation me-2"></i>{{ session('error') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
<!-- Quick Stats Cards Row --> <!-- Quick Stats Cards Row -->
<div class="row g-4 mb-4"> <div class="row g-4 mb-4">
<!-- Courses Card -->
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="card stat-card shadow-sm h-100"> <div class="card stat-card shadow-sm h-100">
<div class="card-body d-flex align-items-center justify-content-between p-4"> <div class="card-body d-flex align-items-center justify-content-between p-4">
@ -71,7 +98,6 @@
</div> </div>
</div> </div>
<!-- Timetable Card -->
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="card stat-card shadow-sm h-100"> <div class="card stat-card shadow-sm h-100">
<div class="card-body d-flex align-items-center justify-content-between p-4"> <div class="card-body d-flex align-items-center justify-content-between p-4">
@ -91,7 +117,6 @@
</div> </div>
</div> </div>
<!-- Exam Results Card -->
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="card stat-card shadow-sm h-100"> <div class="card stat-card shadow-sm h-100">
<div class="card-body d-flex align-items-center justify-content-between p-4"> <div class="card-body d-flex align-items-center justify-content-between p-4">
@ -111,7 +136,6 @@
</div> </div>
</div> </div>
<!-- Notifications Card -->
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="card stat-card shadow-sm h-100"> <div class="card stat-card shadow-sm h-100">
<div class="card-body d-flex align-items-center justify-content-between p-4"> <div class="card-body d-flex align-items-center justify-content-between p-4">
@ -132,10 +156,8 @@
</div> </div>
</div> </div>
<!-- Main Content Area: Quick Navigation & Live Notifications --> <!-- Main Content Area -->
<div class="row g-4"> <div class="row g-4 mb-4">
<!-- Quick Action Shortcuts Panel -->
<div class="col-lg-7"> <div class="col-lg-7">
<div class="card border-0 shadow-sm rounded-3 h-100"> <div class="card border-0 shadow-sm rounded-3 h-100">
<div class="card-header bg-white py-3 border-0"> <div class="card-header bg-white py-3 border-0">
@ -186,37 +208,106 @@
</div> </div>
</div> </div>
<!-- Live Notifications Feed Panel -->
<div class="col-lg-5" id="notifications-section"> <div class="col-lg-5" id="notifications-section">
<div class="card border-0 shadow-sm rounded-3 h-100"> <div class="card border-0 shadow-sm rounded-3 h-100">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center"> <div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
<h5 class="fw-bold mb-0 text-dark"> <div>
<h5 class="fw-bold mb-0 text-dark d-inline me-2">
<i class="fa-solid fa-bell text-danger me-2"></i>System Notifications <i class="fa-solid fa-bell text-danger me-2"></i>System Notifications
</h5> </h5>
<span class="badge bg-danger rounded-pill">{{ $unreadNotifications ?? 0 }} Unread</span> <span class="badge bg-danger rounded-pill">{{ $unreadNotifications ?? 0 }} Unread</span>
</div> </div>
<div class="card-body p-0"> <button class="btn btn-sm btn-primary rounded-3 edit-only" data-bs-toggle="modal" data-bs-target="#addNotificationModal">
<i class="fa-solid fa-plus me-1"></i> Add
</button>
</div>
<div class="card-body p-0" style="max-height: 450px; overflow-y: auto;">
<ul class="list-group list-group-flush"> <ul class="list-group list-group-flush">
@forelse($notifications ?? [] as $notification) @forelse($notifications ?? [] as $notification)
<li class="list-group-item p-3 border-bottom {{ $notification->is_read ? 'bg-light' : '' }}"> <li class="list-group-item p-3 border-bottom {{ $notification->is_read ? 'bg-light text-muted' : 'bg-white' }}">
<div class="d-flex justify-content-between align-items-start"> <div class="d-flex justify-content-between align-items-start">
<div> <div class="me-2">
<h6 class="fw-bold mb-1 text-dark">{{ $notification->title }}</h6> @if($notification->type == 'success')
<span class="badge bg-success mb-1">Success</span>
@elseif($notification->type == 'warning')
<span class="badge bg-warning text-dark mb-1">Warning</span>
@else
<span class="badge bg-info text-dark mb-1">Info</span>
@endif
<h6 class="fw-bold mb-1 {{ $notification->is_read ? 'text-secondary' : 'text-dark' }}">
{{ $notification->title }}
</h6>
<p class="mb-1 text-muted small">{{ $notification->message }}</p> <p class="mb-1 text-muted small">{{ $notification->message }}</p>
<small class="text-secondary opacity-75" style="font-size: 11px;">
<i class="fa-regular fa-clock me-1"></i>{{ $notification->created_at->diffForHumans() }} <small class="text-secondary opacity-75 d-block" style="font-size: 11px;">
<i class="fa-regular fa-clock me-1"></i>{{ $notification->created_at ? $notification->created_at->diffForHumans() : '' }}
</small> </small>
</div> </div>
<div class="d-flex gap-1 align-items-center edit-only">
@if(!$notification->is_read) @if(!$notification->is_read)
<form action="{{ route('admin.notifications.read', $notification->id) }}" method="POST"> <form action="{{ route('admin.notifications.read', $notification->id) }}" method="POST" class="d-inline">
@csrf @csrf
<button type="submit" class="btn btn-sm btn-outline-success" title="Mark as Read"> <button type="submit" class="btn btn-sm btn-outline-success border-0 rounded-circle" title="Mark as Read">
<i class="fa-solid fa-check"></i> <i class="fa-solid fa-check"></i>
</button> </button>
</form> </form>
@else
<span class="text-muted px-1" title="Read"><i class="fa-solid fa-check-double text-secondary"></i></span>
@endif @endif
<button class="btn btn-sm btn-outline-primary border-0 rounded-circle" data-bs-toggle="modal" data-bs-target="#editNotificationModal{{ $notification->id }}" title="Edit">
<i class="fa-solid fa-pen-to-square"></i>
</button>
<form action="{{ route('admin.notifications.destroy', $notification->id) }}" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this notification?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-outline-danger border-0 rounded-circle" title="Delete">
<i class="fa-solid fa-trash-can"></i>
</button>
</form>
</div>
</div> </div>
</li> </li>
<div class="modal fade" id="editNotificationModal{{ $notification->id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title fw-bold"><i class="fa-solid fa-pen-to-square me-2"></i>Edit Notification</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="{{ route('admin.notifications.update', $notification->id) }}" method="POST">
@csrf
@method('PUT')
<div class="modal-body p-4">
<div class="mb-3">
<label class="form-label fw-bold">Notification Type</label>
<select name="type" class="form-select" required>
<option value="info" {{ $notification->type == 'info' ? 'selected' : '' }}>Info</option>
<option value="success" {{ $notification->type == 'success' ? 'selected' : '' }}>Success</option>
<option value="warning" {{ $notification->type == 'warning' ? 'selected' : '' }}>Warning</option>
</select>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Title</label>
<input type="text" name="title" class="form-control" value="{{ $notification->title }}" required>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Message</label>
<textarea name="message" class="form-control" rows="3" required>{{ $notification->message }}</textarea>
</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"><i class="fa-solid fa-save me-1"></i> Update</button>
</div>
</form>
</div>
</div>
</div>
@empty @empty
<li class="list-group-item text-center py-4 text-muted"> <li class="list-group-item text-center py-4 text-muted">
<i class="fa-solid fa-inbox fs-3 d-block mb-2 text-secondary"></i> <i class="fa-solid fa-inbox fs-3 d-block mb-2 text-secondary"></i>
@ -227,7 +318,384 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Registered Users Table -->
<div class="row g-4 mb-4">
<div class="col-12">
<div class="card border-0 shadow-sm rounded-3">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
<h5 class="fw-bold mb-0 text-dark">
<i class="fa-solid fa-users text-primary me-2"></i>Registered System Users List
</h5>
<span class="badge bg-primary rounded-pill">{{ count($users ?? []) }} Users</span>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th class="ps-3">ID</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th class="text-center" style="min-width: 210px;">Portal PIN</th>
<th>Created Date</th>
<th class="text-end pe-3 edit-only">Action</th>
</tr>
</thead>
<tbody>
@forelse($users ?? [] as $user)
<tr>
<td class="ps-3 fw-bold">#{{ $user->id }}</td>
<td>{{ $user->first_name }} {{ $user->last_name }}</td>
<td>{{ $user->email }}</td>
<td>{{ $user->phone ?? 'N/A' }}</td>
<!-- DIRECT PIN EDIT / ADD / DELETE CONTROLS INSIDE CELL -->
<td class="text-center">
<div class="d-flex align-items-center justify-content-center gap-2">
@if(!empty($log->pin) && $log->pin !== 'N/A')
<!-- Current PIN Badge -->
<span class="badge bg-dark font-monospace fs-6 px-3 py-2 shadow-sm">
<i class="fa-solid fa-key text-warning me-1"></i>{{ $log->pin }}
</span>
<!-- Direct Edit PIN Button -->
<button class="btn btn-sm btn-outline-primary border-0 rounded-circle"
data-bs-toggle="modal"
data-bs-target="#editPinModal{{ $log->id }}"
title="Edit PIN">
<i class="fa-solid fa-pen-to-square"></i>
</button>
<!-- Direct Delete PIN Form/Button -->
<form action="{{ route('admin.portallogs.update', $log->id) }}" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this PIN?');">
@csrf
@method('PUT')
<input type="hidden" name="pin" value="">
<button type="submit" class="btn btn-sm btn-outline-danger border-0 rounded-circle" title="Delete PIN">
<i class="fa-solid fa-trash-can"></i>
</button>
</form>
@else
<!-- N/A Badge -->
<span class="badge bg-secondary font-monospace fs-6 px-3 py-2 opacity-75">
<i class="fa-solid fa-key text-white-50 me-1"></i>N/A
</span>
<!-- Direct Add New PIN Button -->
<button class="btn btn-sm btn-success rounded-pill px-2 py-1"
data-bs-toggle="modal"
data-bs-target="#editPinModal{{ $log->id }}">
<i class="fa-solid fa-plus me-1"></i>Add PIN
</button>
@endif
</div>
</td>
<td>{{ $log->created_at ? $log->created_at->format('Y-m-d H:i') : 'N/A' }}</td>
<td class="text-end pe-3 edit-only">
<form action="{{ route('admin.portallogs.send', $user->id) }}" method="POST" class="d-inline">
@csrf
<button type="submit" class="btn btn-sm btn-primary rounded-2">
<i class="fa-solid fa-paper-plane me-1"></i> Send to Portal Log
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center py-4 text-muted">
<i class="fa-solid fa-user-slash fs-3 d-block mb-2 text-secondary"></i>
No registered users found.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Student Portal Active Logs Table (PIN, Add, Edit & Delete) -->
<div class="row g-4" id="portal-logs-section">
<div class="col-12">
<div class="card border-0 shadow-sm rounded-3">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
<h5 class="fw-bold mb-0 text-dark">
<i class="fa-solid fa-address-card text-success me-2"></i>Student Portal Active Logs
</h5>
<div class="d-flex align-items-center gap-2">
<span class="badge bg-success rounded-pill">{{ count($portalLogs ?? []) }} Active Student Logs</span>
<!-- Add New Portal Log Button (Edit mode එකේදී විතරක් පෙනේ) -->
<button class="btn btn-sm btn-success rounded-3 edit-only" data-bs-toggle="modal" data-bs-target="#addPortalLogModal">
<i class="fa-solid fa-plus me-1"></i> Add Portal Log
</button>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th class="ps-3">Log ID</th>
<th>User ID</th>
<th>Student Name</th>
<th>Email</th>
<th>Phone</th>
<th class="text-center">Portal PIN</th>
<th>Created Date</th>
<th class="text-end pe-3">Actions</th>
</tr>
</thead>
<tbody>
@forelse($portalLogs ?? [] as $log)
<tr>
<td class="ps-3 fw-bold">#LOG-{{ $log->id }}</td>
<td>#{{ $log->user_id }}</td>
<td>
<div class="fw-bold text-dark">
@if(!empty($log->first_name) || !empty($log->last_name))
{{ trim($log->first_name . ' ' . $log->last_name) }}
@elseif($log->user)
{{ $log->user->name ?? trim($log->user->first_name . ' ' . $log->user->last_name) }}
@else
<span class="text-muted">N/A</span>
@endif
</div>
</td>
<td>{{ $log->email }}</td>
<td>{{ $log->phone ?? ($log->user->phone ?? 'N/A') }}</td>
<!-- PIN Column -->
<td class="text-center">
<span class="badge bg-dark font-monospace fs-6 px-3 py-1">
<i class="fa-solid fa-key me-1 text-warning"></i>{{ $log->pin ?? 'N/A' }}
</span>
</td>
<td>{{ $log->created_at ? $log->created_at->format('Y-m-d H:i') : 'N/A' }}</td>
<td class="text-end pe-3">
<!-- Edit / Delete (Edit mode නොමැති විට Status එක පමණක් පෙනේ) -->
<span class="badge bg-success bg-opacity-10 text-success border border-success px-3 py-2 rounded-pill edit-hidden">
<i class="fa-solid fa-circle-check me-1"></i> Active
</span>
<div class="d-flex gap-1 justify-content-end edit-only">
<!-- Edit PIN Modal Trigger Button -->
<button class="btn btn-sm btn-outline-primary rounded-2" data-bs-toggle="modal" data-bs-target="#editPortalLogModal{{ $log->id }}" title="Edit Student PIN / Details">
<i class="fa-solid fa-pen-to-square me-1"></i> Edit
</button>
<!-- Delete Log Button -->
<form action="{{ route('admin.portallogs.destroy', $log->id) }}" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this Student Portal Log?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-outline-danger rounded-2" title="Delete Log">
<i class="fa-solid fa-trash-can"></i>
</button>
</form>
</div>
</td>
</tr>
<!-- Edit Portal Log Modal -->
<div class="modal fade" id="editPortalLogModal{{ $log->id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title fw-bold"><i class="fa-solid fa-key me-2"></i>Edit Portal Log & PIN</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="{{ route('admin.portallogs.update', $log->id) }}" method="POST">
@csrf
@method('PUT')
<div class="modal-body p-4">
<div class="mb-3">
<label class="form-label fw-bold">Student Name</label>
<input type="text" class="form-control" value="{{ trim($log->first_name . ' ' . $log->last_name) }}" readonly disabled>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Email Address</label>
<input type="email" name="email" class="form-control" value="{{ $log->email }}" required>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Phone Number</label>
<input type="text" name="phone" class="form-control" value="{{ $log->phone }}">
</div>
<div class="mb-3">
<label class="form-label fw-bold text-primary"><i class="fa-solid fa-lock me-1"></i> Student Portal PIN</label>
<input type="text" name="pin" class="form-control fw-bold fs-5 text-center text-primary" maxlength="6" value="{{ $log->pin }}" placeholder="Enter PIN (e.g. 1234)" required>
</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"><i class="fa-solid fa-save me-1"></i> Save Changes</button>
</div>
</form>
</div>
</div>
</div>
@empty
<tr>
<td colspan="8" class="text-center py-4 text-muted">
<i class="fa-solid fa-folder-open fs-3 d-block mb-2 text-secondary"></i>
No active student portal logs found.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Add New Portal Log Modal -->
<div class="modal fade" id="addPortalLogModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-success text-white">
<h5 class="modal-title fw-bold"><i class="fa-solid fa-user-plus me-2"></i>Add Student Portal Access</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="{{ route('admin.portallogs.store') }}" method="POST">
@csrf
<div class="modal-body p-4">
<div class="mb-3">
<label class="form-label fw-bold">Select User / Student</label>
<select name="user_id" class="form-select" required>
<option value="">-- Choose System User --</option>
@foreach($users ?? [] as $u)
<option value="{{ $u->id }}">{{ $u->first_name }} {{ $u->last_name }} ({{ $u->email }})</option>
@endforeach
</select>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Email Address</label>
<input type="email" name="email" class="form-control" placeholder="student@example.com" required>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Phone Number</label>
<input type="text" name="phone" class="form-control" placeholder="07XXXXXXXX">
</div>
<div class="mb-3">
<label class="form-label fw-bold text-success"><i class="fa-solid fa-key me-1"></i> Assign Portal PIN</label>
<input type="text" name="pin" class="form-control fw-bold fs-5 text-center text-success" maxlength="6" placeholder="Enter PIN (e.g. 5678)" required>
</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-success"><i class="fa-solid fa-plus-circle me-1"></i> Create Access</button>
</div>
</form>
</div>
</div> </div>
</div> </div>
<!-- Add Notification Modal -->
<div class="modal fade" id="addNotificationModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title fw-bold"><i class="fa-solid fa-plus-circle me-2"></i>Create New Notification</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="{{ route('admin.notifications.store') }}" method="POST">
@csrf
<div class="modal-body p-4">
<div class="mb-3">
<label class="form-label fw-bold">Notification Type</label>
<select name="type" class="form-select" required>
<option value="info">Info</option>
<option value="success">Success</option>
<option value="warning">Warning</option>
</select>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Title</label>
<input type="text" name="title" class="form-control" placeholder="Enter title" required>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Message</label>
<textarea name="message" class="form-control" rows="3" placeholder="Enter notification message..." required></textarea>
</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"><i class="fa-solid fa-paper-plane me-1"></i> Send Notification</button>
</div>
</form>
</div>
</div>
</div>
<!--
<script>
document.addEventListener("DOMContentLoaded", function () {
const ADMIN_PIN = "1234";
const dashboardWrapper = document.getElementById("dashboardWrapper");
const inputPin = document.getElementById("inputPin");
const btnSubmitPin = document.getElementById("btnSubmitPin");
const pinErrorMessage = document.getElementById("pinErrorMessage");
const btnLockToggle = document.getElementById("btnLockToggle");
const lockIcon = document.getElementById("lockIcon");
const lockStatusText = document.getElementById("lockStatusText");
const pinModal = new bootstrap.Modal(document.getElementById('pinAuthModal'));
if (sessionStorage.getItem("adminEditUnlocked") === "true") {
enableEditMode();
}
function verifyPin() {
if (inputPin.value.trim() === ADMIN_PIN) {
sessionStorage.setItem("adminEditUnlocked", "true");
enableEditMode();
pinModal.hide();
inputPin.value = "";
pinErrorMessage.classList.add("d-none");
} else {
pinErrorMessage.classList.remove("d-none");
inputPin.value = "";
inputPin.focus();
}
}
btnSubmitPin.addEventListener("click", verifyPin);
inputPin.addEventListener("keypress", function (e) {
if (e.key === "Enter") {
verifyPin();
}
});
function enableEditMode() {
dashboardWrapper.classList.add("is-editing");
btnLockToggle.classList.replace("btn-warning", "btn-success");
lockIcon.classList.replace("fa-lock", "fa-lock-open");
lockStatusText.innerText = "Edit Mode Active";
btnLockToggle.removeAttribute("data-bs-toggle");
btnLockToggle.removeAttribute("data-bs-target");
}
btnLockToggle.addEventListener("click", function () {
if (dashboardWrapper.classList.contains("is-editing")) {
sessionStorage.removeItem("adminEditUnlocked");
dashboardWrapper.classList.remove("is-editing");
btnLockToggle.classList.replace("btn-success", "btn-warning");
lockIcon.classList.replace("fa-lock-open", "fa-lock");
lockStatusText.innerText = "Unlock Edit Mode";
btnLockToggle.setAttribute("data-bs-toggle", "modal");
btnLockToggle.setAttribute("data-bs-target", "#pinAuthModal");
}
});
});
</script> -->
@endsection @endsection

View File

@ -7,7 +7,7 @@
<title>@yield('title', 'Admin Portal - Automobile Academic')</title> <title>@yield('title', 'Admin Portal - Automobile Academic')</title>
<!-- Font Awesome Icons & Bootstrap 5 --> <!-- Font Awesome Icons & Bootstrap 5 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" integrity="sha512-Kc323vGBEqzTmouAECnVceyQqyqdsSiqLQISBL29aUW4U/M7pSPA/gEUZQqv1cwx4OnYxTxve5UMg5GT6L4JJg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style> <style>

View File

@ -522,28 +522,50 @@
// Logout Logic // Logout Logic
function submitLogout() { function submitLogout() {
const tokenEl = document.querySelector('meta[name="csrf-token"]'); const tokenEl = document.querySelector('meta[name="csrf-token"]');
const csrfToken = tokenEl ? tokenEl.getAttribute('content') : '';
if (!csrfToken) {
console.error('CSRF token meta tag not found!');
alert('Security token missing. Refreshing page...');
window.location.reload();
return;
}
fetch('/studentlogout', { fetch('/studentlogout', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json', 'Accept': 'application/json',
'X-CSRF-TOKEN': tokenEl ? tokenEl.getAttribute('content') : '' 'X-CSRF-TOKEN': csrfToken
} }
}) })
.then(res => res.json()) .then(res => {
if (res.status === 419) {
alert('Session expired. Page will reload.');
window.location.reload();
return;
}
if (!res.ok) {
throw new Error(`Server returned status ${res.status}`);
}
return res.json();
})
.then(data => { .then(data => {
if (data.success) { if (data && data.success) {
window.location.href = '/'; window.location.href = '/';
} else { } else {
alert('Logout failed. Please try again.'); alert('Logout failed. Please try again.');
} }
}) })
.catch(err => { .catch(err => {
console.error('Error:', err); console.error('Logout Error:', err);
alert('Server side error occurred during logout.'); alert('An error occurred during logout.');
}); });
} }
</script>
</script>
</body> </body>
</html> </html>

View File

@ -892,7 +892,7 @@ body {
<p class="mb-4 text-white-90 fs-5">Driving Innovation Through Education, Research &amp; Technology</p> <p class="mb-4 text-white-90 fs-5">Driving Innovation Through Education, Research &amp; Technology</p>
<div class="d-flex flex-wrap justify-content-center gap-3"> <div class="d-flex flex-wrap justify-content-center gap-3">
<a href="{{ Route::has('apply') ? route('apply') : '#' }}" class="btn btn-theme"> <a href="{{ Route::has('apply') ? route('apply') : '/apply' }}" class="btn btn-theme">
<i class="fa-solid fa-user-plus me-2"></i>Apply Now <i class="fa-solid fa-user-plus me-2"></i>Apply Now
</a> </a>
<a href="/courses" class="btn btn-outline-theme"> <a href="/courses" class="btn btn-outline-theme">
@ -1150,7 +1150,7 @@ body {
<p class="mb-4 max-width-600 mx-auto">Shape your technical career through modern innovation, practical mastery, and expert guidance.</p> <p class="mb-4 max-width-600 mx-auto">Shape your technical career through modern innovation, practical mastery, and expert guidance.</p>
<div class="cta-actions"> <div class="cta-actions">
<a href="{{ Route::has('apply') ? route('apply') : '#' }}" class="btn btn-theme" style="background: #822424; border: 2px solid #FFF;"> <a href="{{ Route::has('apply') ? route('apply') : '/apply' }}" class="btn btn-theme" style="background: #822424; border: 2px solid #FFF;">
<i class="fa-solid fa-user-plus me-2"></i>Apply Now <i class="fa-solid fa-user-plus me-2"></i>Apply Now
</a> </a>
<a href="/courses" class="btn btn-outline-theme"> <a href="/courses" class="btn btn-outline-theme">

View File

@ -27,16 +27,7 @@ use App\Http\Controllers\DocumentDownloadController;
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
Route::get('/', function () {
return view('welcome');
});
Route::get('/', [welcomeController::class, 'index'])->name('welcome'); Route::get('/', [welcomeController::class, 'index'])->name('welcome');
Route::get('/courses', [coursesController::class, 'index'])->name('courses.index'); Route::get('/courses', [coursesController::class, 'index'])->name('courses.index');
Route::view('/students', 'students')->name('students'); Route::view('/students', 'students')->name('students');
Route::view('/abouts', 'abouts')->name('abouts'); Route::view('/abouts', 'abouts')->name('abouts');
@ -91,12 +82,7 @@ Route::post('/student-login', [StudentPortalController::class, 'studentlogin'])-
Route::post('/studentlogout', [StudentPortalController::class, 'studentlogout'])->name('student.logout'); Route::post('/studentlogout', [StudentPortalController::class, 'studentlogout'])->name('student.logout');
Route::middleware(['web'])->group(function () { Route::middleware(['web'])->group(function () {
Route::get('/student-portal', function () { Route::get('/student-portal', [StudentPortalController::class, 'index'])->name('student.portal');
if (!session()->has('student_id') && !session()->has('student_log_id')) {
return redirect('/signin')->with('error', 'Please login first!');
}
return view('StudentPortal');
})->name('student.portal');
Route::get('/StudentProfile', [StudentPortalController::class, 'showProfile'])->name('StudentProfile'); Route::get('/StudentProfile', [StudentPortalController::class, 'showProfile'])->name('StudentProfile');
Route::post('/StudentProfile/update', [StudentPortalController::class, 'updateProfile'])->name('student.profile.update'); Route::post('/StudentProfile/update', [StudentPortalController::class, 'updateProfile'])->name('student.profile.update');
@ -150,8 +136,11 @@ Route::post('/admin/logout', [AdminAuthController::class, 'logout'])->name('admi
Route::middleware(['web'])->prefix('admin')->group(function () { Route::middleware(['web'])->prefix('admin')->group(function () {
// Admin Dashboard Routes // Admin Dashboard & Notifications Routes
Route::get('/dashboard', [AdminDashboardController::class, 'index'])->name('admin.admindashboard'); Route::get('/dashboard', [AdminDashboardController::class, 'index'])->name('admin.dashboard');
Route::post('/notifications', [AdminDashboardController::class, 'store'])->name('admin.notifications.store');
Route::put('/notifications/{id}', [AdminDashboardController::class, 'update'])->name('admin.notifications.update');
Route::delete('/notifications/{id}', [AdminDashboardController::class, 'destroy'])->name('admin.notifications.destroy');
Route::post('/notifications/read/{id}', [AdminDashboardController::class, 'markAsRead'])->name('admin.notifications.read'); Route::post('/notifications/read/{id}', [AdminDashboardController::class, 'markAsRead'])->name('admin.notifications.read');
// Admin Courses // Admin Courses
@ -176,5 +165,13 @@ Route::middleware(['web'])->prefix('admin')->group(function () {
Route::get('/profile', [AdminProfileController::class, 'index'])->name('admin.profile'); Route::get('/profile', [AdminProfileController::class, 'index'])->name('admin.profile');
Route::put('/profile/update', [AdminProfileController::class, 'update'])->name('admin.profile.update'); Route::put('/profile/update', [AdminProfileController::class, 'update'])->name('admin.profile.update');
Route::put('/profile/password', [AdminProfileController::class, 'updatePassword'])->name('admin.profile.password'); Route::put('/profile/password', [AdminProfileController::class, 'updatePassword'])->name('admin.profile.password');
Route::post('/admin/portal-log/store', [AdminDashboardController::class, 'storePortalLog'])->name('admin.portal.store');
Route::post('/admin/portal-logs/send/{userId}', [AdminDashboardController::class, 'sendToPortalLog'])->name('admin.portallogs.send');
Route::prefix('admin')->name('admin.')->group(function () {
Route::post('/portal-logs', [PortalLogController::class, 'store'])->name('portallogs.store');
Route::put('/portal-logs/{id}', [PortalLogController::class, 'update'])->name('portallogs.update');
Route::delete('/portal-logs/{id}', [PortalLogController::class, 'destroy'])->name('portallogs.destroy');
});
}); });