set result and timetable pages

This commit is contained in:
imadhigp 2026-08-05 10:21:54 +05:30
parent 5d76c711f7
commit 41934b31fc
9 changed files with 494 additions and 268 deletions

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Result;
class ResultsController extends Controller
{
public function index()
{
// 1. Session එකෙන් ID එක ලබා ගැනීම
$studentLogId = session('student_log_id') ?? session('student_id');
// Session එකේ ID එකක් නොමැති නම් Login එකට Redirect කිරීම
if (!$studentLogId) {
return redirect('/signin')->with('error', 'Please login first!');
}
// 2. Log වී සිටින Student ගේ student_log_id එකට අදාළව දත්ත ලබා ගැනීම
$results = Result::where('student_log_id', $studentLogId)->get();
// 3. Calculations
$totalModules = $results->count();
$passedModules = $results->filter(function ($item) {
return strtolower(trim($item->status)) === 'pass';
})->count();
$averageMarks = $totalModules > 0 ? round($results->avg('marks'), 1) : 0;
// View එකට Data Pass කිරීම
return view('results', compact('results', 'totalModules', 'passedModules', 'averageMarks'));
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Timetable;
use Carbon\Carbon;
class TimetableController extends Controller
{
public function index()
{
// Session Check
$studentLogId = session('student_log_id') ?? session('student_id');
if (!$studentLogId) {
return redirect('/signin')->with('error', 'Please login first!');
}
// 1. All Timetable Records Get
// (තනි Student ට අදාළ දත්ත හෝ පොදු Timetable දත්ත)
$rawTimetables = Timetable::whereNull('student_log_id')
->orWhere('student_log_id', $studentLogId)
->orderBy('sort_order', 'asc')
->get();
// Data එක Table එකට ලෙහෙසියෙන් Group කර ගැනීම
// Array structure: $schedule[TimeSlot][Day] = Item
$schedule = [];
$timeSlots = [];
foreach ($rawTimetables as $item) {
if (!in_array($item->time_slot, $timeSlots)) {
$timeSlots[] = $item->time_slot;
}
$schedule[$item->time_slot][$item->day] = $item;
}
// 2. Today's Schedule (අද දවසේ පන්ති පමණක් ලබා ගැනීම)
$todayName = Carbon::now()->format('l'); // Monday, Tuesday etc.
$todaySchedule = Timetable::where('day', $todayName)
->where(function($q) use ($studentLogId) {
$q->whereNull('student_log_id')
->orWhere('student_log_id', $studentLogId);
})
->where('type', '!=', 'Break')
->orderBy('sort_order', 'asc')
->get();
$days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
return view('timetable', compact('schedule', 'timeSlots', 'days', 'todaySchedule', 'todayName'));
}
}

25
app/Models/Result.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Result extends Model
{
use HasFactory;
protected $table = 'results';
protected $fillable = [
'student_log_id',
'module_code',
'academic_year',
'semester',
'module_name',
'marks',
'grade',
'status',
];
}

22
app/Models/Timetable.php Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Timetable extends Model
{
use HasFactory;
protected $table = 'timetables';
protected $fillable = [
'student_log_id',
'time_slot',
'day',
'subject_name',
'type',
'sort_order',
];
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('results', function (Blueprint $table) {
$table->id();
$table->foreignId('student_log_id')->constrained('student_portal_logs')->onDelete('cascade');
$table->string('module_code');
$table->string('academic_year');
$table->string('semester');
$table->string('module_name');
$table->integer('marks');
$table->string('grade'); // e.g., 'A+', 'A', 'B', 'C', 'F'
$table->string('status')->default('Pass'); // 'Pass' or 'Fail'
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('results');
}
};

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('timetables', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('student_log_id')->nullable(); // Student-specific Timetable එකක් නම්
$table->string('time_slot');
$table->string('day');
$table->string('subject_name');
$table->enum('type', ['Theory', 'Practical', 'Workshop', 'Break'])->default('Theory');
$table->integer('sort_order')->default(1);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('timetables');
}
};

View File

@ -13,6 +13,9 @@
<!-- Google Font -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- html2pdf Library for Client-side PDF Generation -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<style>
:root {
--theme-navy: #2D355B; /* Primary Navy */
@ -152,6 +155,11 @@ Result Table
font-weight: 600;
}
.status-fail {
color: #dc3545;
font-weight: 600;
}
/* =======================
Progress
======================= */
@ -169,6 +177,11 @@ Progress
background-color: var(--theme-red) !important;
}
.align-items-stretch {
align-items: stretch !important;
padding-top: 38px;
}
/* =======================
Download Card
======================= */
@ -216,163 +229,195 @@ Download Card
<div class="container py-4">
<!-- Hero Section -->
<div class="result-hero">
<h2>
<i class="fas fa-award me-2" style="color: var(--theme-yellow);"></i>
Academic Results
</h2>
<p>
View your examination results and overall academic performance.
</p>
</div>
<!-- Printable Area Container -->
<div id="pdf-download-area">
<!-- Summary Cards -->
<div class="row g-4">
<div class="col-md-4">
<div class="summary-card">
<i class="fas fa-chart-line"></i>
<h3>82%</h3>
<small>Overall Average</small>
</div>
</div>
<div class="col-md-4">
<div class="summary-card">
<i class="fas fa-medal"></i>
<h3>3.65</h3>
<small>Current GPA</small>
</div>
</div>
<div class="col-md-4">
<div class="summary-card">
<i class="fas fa-book-open"></i>
<h3>6 / 6</h3>
<small>Modules Passed</small>
</div>
</div>
</div>
<!-- Result Table -->
<div class="result-card">
<div class="table-head">
<h5 class="mb-0 fw-bold">
Semester Results
</h5>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Module</th>
<th>Marks</th>
<th>Grade</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td class="fw-semibold" style="color: var(--theme-navy);">Engine Technology</td>
<td>91</td>
<td>
<span class="grade a">A+</span>
</td>
<td><span class="status-pass"><i class="fas fa-check-circle me-1"></i>Pass</span></td>
</tr>
<tr>
<td class="fw-semibold" style="color: var(--theme-navy);">Brake Systems</td>
<td>84</td>
<td>
<span class="grade a">A</span>
</td>
<td><span class="status-pass"><i class="fas fa-check-circle me-1"></i>Pass</span></td>
</tr>
<tr>
<td class="fw-semibold" style="color: var(--theme-navy);">Electrical Systems</td>
<td>79</td>
<td>
<span class="grade b">B+</span>
</td>
<td><span class="status-pass"><i class="fas fa-check-circle me-1"></i>Pass</span></td>
</tr>
<tr>
<td class="fw-semibold" style="color: var(--theme-navy);">Transmission Systems</td>
<td>74</td>
<td>
<span class="grade b">B</span>
</td>
<td><span class="status-pass"><i class="fas fa-check-circle me-1"></i>Pass</span></td>
</tr>
<tr>
<td class="fw-semibold" style="color: var(--theme-navy);">Workshop Practice</td>
<td>88</td>
<td>
<span class="grade a">A</span>
</td>
<td><span class="status-pass"><i class="fas fa-check-circle me-1"></i>Pass</span></td>
</tr>
<tr>
<td class="fw-semibold" style="color: var(--theme-navy);">Industrial Safety</td>
<td>81</td>
<td>
<span class="grade a">A-</span>
</td>
<td><span class="status-pass"><i class="fas fa-check-circle me-1"></i>Pass</span></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Performance Bars -->
<div class="result-card">
<div class="table-head">
<h5 class="mb-0 fw-bold">
Overall Performance
</h5>
</div>
<div class="p-4">
<div class="d-flex justify-content-between mb-2">
<span class="fw-semibold" style="color: var(--theme-navy);">Course Completion</span>
<span class="fw-bold" style="color: var(--theme-navy);">82%</span>
</div>
<div class="progress mb-4">
<div class="progress-bar progress-bar-navy" style="width:82%"></div>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="fw-semibold" style="color: var(--theme-navy);">Attendance Score</span>
<span class="fw-bold" style="color: var(--theme-red);">90%</span>
</div>
<div class="progress">
<div class="progress-bar progress-bar-red" style="width:90%"></div>
</div>
</div>
</div>
<!-- Download Section -->
<div class="download-card">
<div>
<h4>Download Official Result Sheet</h4>
<p class="mb-0">
Get your latest semester examination results in official PDF format.
<!-- Hero Section -->
<div class="result-hero">
<h2>
<i class="fas fa-award me-2" style="color: var(--theme-yellow);"></i>
Academic Results
</h2>
<p>
View your examination results and overall academic performance.
</p>
</div>
<button class="btn btn-result shadow-sm">
<i class="fas fa-download me-2"></i>Download PDF
</button>
<!-- Summary Cards -->
<div class="row g-4">
<div class="col-md-4">
<div class="summary-card">
<i class="fas fa-chart-line"></i>
<h3>{{ $averageMarks ?? 0 }}%</h3>
<small>Overall Average</small>
</div>
</div>
<div class="col-md-4">
<div class="summary-card">
<i class="fas fa-medal"></i>
<h3>{{ $gpa ?? '3.65' }}</h3>
<small>Current GPA</small>
</div>
</div>
<div class="col-md-4">
<div class="summary-card">
<i class="fas fa-book-open"></i>
<h3>{{ $passedModules ?? 0 }} / {{ $totalModules ?? 0 }}</h3>
<small>Modules Passed</small>
</div>
</div>
</div>
<!-- Result Table -->
<div class="result-card">
<div class="table-head d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold">
Semester Results
</h5>
@if(isset($results) && $results->isNotEmpty())
<span class="badge bg-light text-dark">
{{ optional($results->first())->academic_year }} | {{ optional($results->first())->semester }}
</span>
@endif
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Module Code</th>
<th>Module Name</th>
<th>Marks</th>
<th>Grade</th>
<th>Status</th>
</tr>
</thead>
<tbody>
@forelse ($results ?? [] as $item)
<tr>
<td class="fw-bold" style="color: var(--theme-navy);">{{ $item->module_code }}</td>
<td class="fw-semibold">{{ $item->module_name }}</td>
<td>{{ $item->marks }}</td>
<td>
@php
$gradeClass = 'b';
$upperGrade = strtoupper($item->grade ?? '');
if(str_contains($upperGrade, 'A')) {
$gradeClass = 'a';
} elseif(str_contains($upperGrade, 'F')) {
$gradeClass = 'fail';
} elseif(str_contains($upperGrade, 'C')) {
$gradeClass = 'c';
}
@endphp
<span class="grade {{ $gradeClass }}">{{ $item->grade }}</span>
</td>
<td>
@if(strtolower($item->status ?? '') == 'pass')
<span class="status-pass"><i class="fas fa-check-circle me-1"></i>Pass</span>
@else
<span class="status-fail"><i class="fas fa-times-circle me-1"></i>Fail</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center py-4 text-muted">
<i class="fas fa-info-circle me-1"></i> No result records found for your account.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div> <!-- End Printable Area Container -->
<!-- Performance & Download Section (Side by Side) -->
<div class="row g-4 align-items-stretch">
<!-- Overall Performance Card -->
<div class="col-md-6">
<div class="result-card h-100 mt-0">
<div class="table-head">
<h5 class="mb-0 fw-bold">
Overall Performance
</h5>
</div>
<div class="p-4 d-flex flex-column justify-content-center">
<div class="d-flex justify-content-between mb-2">
<span class="fw-semibold" style="color: var(--theme-navy);">Course Completion</span>
<span class="fw-bold" style="color: var(--theme-navy);">{{ $averageMarks ?? 0 }}%</span>
</div>
<div class="progress mb-4">
<div class="progress-bar progress-bar-navy" style="width: {{ $averageMarks ?? 0 }}%"></div>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="fw-semibold" style="color: var(--theme-navy);">Attendance Score</span>
<span class="fw-bold" style="color: var(--theme-red);">90%</span>
</div>
<div class="progress">
<div class="progress-bar progress-bar-red" style="width: 90%"></div>
</div>
</div>
</div>
</div>
<!-- Download Section Card -->
<div class="col-md-6">
<div class="download-card h-100 mt-0 flex-column justify-content-between align-items-start text-start">
<div>
<h4 class="mb-3">Download Official Result Sheet</h4>
<p class="mb-4">
Get your latest semester examination results in official PDF format.
</p>
</div>
<button id="download-pdf-btn" onclick="generatePDF()" class="btn btn-result shadow-sm w-100">
<i class="fas fa-download me-2"></i>Download PDF
</button>
</div>
</div>
</div>
</div>
<!-- JavaScript to handle PDF Download -->
<script>
function generatePDF() {
const element = document.getElementById('pdf-download-area');
const button = document.getElementById('download-pdf-btn');
// Button status change while generating
button.disabled = true;
button.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Generating PDF...';
const options = {
margin: [10, 10, 10, 10],
filename: 'Official_Result_Sheet.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
};
// Generate PDF and restore button state
html2pdf().set(options).from(element).save().then(() => {
button.disabled = false;
button.innerHTML = '<i class="fas fa-download me-2"></i>Download PDF';
}).catch(err => {
console.error(err);
button.disabled = false;
button.innerHTML = '<i class="fas fa-download me-2"></i>Download PDF';
});
}
</script>
@endsection

View File

@ -202,26 +202,33 @@ body {
<div class="today-card">
<h5 class="mb-3">
<i class="fas fa-clock me-2" style="color: var(--theme-red);"></i>
Today's Schedule
Today's Schedule ({{ $todayName ?? 'Today' }})
</h5>
<div class="mb-3 p-2 rounded" style="background: #f8fafc; border-left: 3px solid var(--theme-navy);">
<strong>08:30 - 10:30</strong><br>
<span class="badge badge-theory me-1">Theory</span>
<span>Engine Fundamentals</span>
</div>
@forelse($todaySchedule ?? [] as $todayItem)
@php
$borderStyle = 'var(--theme-navy)';
$badgeClass = 'badge-theory';
if(strtolower($todayItem->type) == 'practical') {
$borderStyle = 'var(--theme-red)';
$badgeClass = 'badge-practical';
} elseif(strtolower($todayItem->type) == 'workshop') {
$borderStyle = '#e67e22';
$badgeClass = 'badge-workshop';
}
@endphp
<div class="mb-3 p-2 rounded" style="background: #f8fafc; border-left: 3px solid #e67e22;">
<strong>10:45 - 12:30</strong><br>
<span class="badge badge-workshop me-1">Workshop</span>
<span>Practical Session</span>
</div>
<div class="p-2 rounded" style="background: #f8fafc; border-left: 3px solid var(--theme-red);">
<strong>01:30 - 03:30</strong><br>
<span class="badge badge-practical me-1">Practical</span>
<span>Electrical System</span>
</div>
<div class="mb-3 p-2 rounded" style="background: #f8fafc; border-left: 3px solid {{ $borderStyle }};">
<strong>{{ $todayItem->time_slot }}</strong><br>
<span class="badge {{ $badgeClass }} me-1">{{ $todayItem->type }}</span>
<span>{{ $todayItem->subject_name }}</span>
</div>
@empty
<div class="p-3 text-center text-muted border rounded">
<i class="fas fa-coffee me-1"></i> No classes scheduled for today!
</div>
@endforelse
</div>
</div>
@ -269,92 +276,65 @@ body {
<thead>
<tr>
<th>Time</th>
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
@foreach($days as $day)
<th class="{{ ($todayName ?? '') == $day ? 'bg-danger text-white' : '' }}">
{{ $day }} {{ ($todayName ?? '') == $day ? '(Today)' : '' }}
</th>
@endforeach
</tr>
</thead>
<tbody>
<tr>
<th>08:30 - 10:30</th>
<td>
<span class="badge badge-theory mb-1">Theory</span><br>
Engine Fundamentals
</td>
<td>
<span class="badge badge-practical mb-1">Practical</span><br>
Engine Lab
</td>
<td>
<span class="badge badge-theory mb-1">Theory</span><br>
Transmission
</td>
<td>
<span class="badge badge-workshop mb-1">Workshop</span><br>
Engine Repair
</td>
<td>
<span class="badge badge-theory mb-1">Theory</span><br>
Vehicle Safety
</td>
</tr>
@forelse($timeSlots as $slot)
@php
// Lunch/Break Check
$isBreakRow = false;
foreach($days as $d) {
if(isset($schedule[$slot][$d]) && strtolower($schedule[$slot][$d]->type) == 'break') {
$isBreakRow = true;
break;
}
}
@endphp
<tr>
<th>10:45 - 12:30</th>
<td>
<span class="badge badge-workshop mb-1">Workshop</span><br>
Service Practice
</td>
<td>
<span class="badge badge-theory mb-1">Theory</span><br>
Electrical
</td>
<td>
<span class="badge badge-practical mb-1">Practical</span><br>
Diagnostics
</td>
<td>
<span class="badge badge-theory mb-1">Theory</span><br>
Fuel System
</td>
<td>
<span class="badge badge-workshop mb-1">Workshop</span><br>
Engine Assembly
</td>
</tr>
<tr class="lunch-row">
<th>12:30 - 01:30</th>
<td colspan="5">
🍴 LUNCH BREAK
</td>
</tr>
<tr>
<th>01:30 - 03:30</th>
<td>
<span class="badge badge-practical mb-1">Practical</span><br>
Electrical System
</td>
<td>
<span class="badge badge-workshop mb-1">Workshop</span><br>
Welding
</td>
<td>
<span class="badge badge-theory mb-1">Theory</span><br>
Auto Electronics
</td>
<td>
<span class="badge badge-practical mb-1">Practical</span><br>
Brake System
</td>
<td>
<span class="badge badge-theory mb-1">Theory</span><br>
Revision
</td>
</tr>
@if($isBreakRow)
<tr class="lunch-row">
<th>{{ $slot }}</th>
<td colspan="5">
🍴 LUNCH BREAK / REST TIME
</td>
</tr>
@else
<tr>
<th>{{ $slot }}</th>
@foreach($days as $day)
<td>
@if(isset($schedule[$slot][$day]))
@php
$cell = $schedule[$slot][$day];
$typeClass = 'badge-theory';
if(strtolower($cell->type) == 'practical') {
$typeClass = 'badge-practical';
} elseif(strtolower($cell->type) == 'workshop') {
$typeClass = 'badge-workshop';
}
@endphp
<span class="badge {{ $typeClass }} mb-1">{{ $cell->type }}</span><br>
{{ $cell->subject_name }}
@else
<span class="text-muted">-</span>
@endif
</td>
@endforeach
</tr>
@endif
@empty
<tr>
<td colspan="6" class="text-center py-4 text-muted">
<i class="fas fa-info-circle me-1"></i> No timetable data available right now.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>

View File

@ -9,6 +9,8 @@ use App\Http\Controllers\studentportalnavController;
use App\Http\Controllers\Auth\ForgotPasswordController;
use App\Http\Controllers\coursesController;
use App\Http\Controllers\ApplyController;
use App\Http\Controllers\ResultsController;
use App\Http\Controllers\TimetableController; // 1. TimetableController එක Import කළා
/*
|--------------------------------------------------------------------------
@ -78,49 +80,44 @@ Route::post('/studentlogout', [StudentPortalController::class, 'studentlogout'])
// Student Portal Section (Protected by Student Session Check)
Route::middleware(['web'])->group(function () {
// Main Portal Landing Page
Route::get('/student-portal', function () {
if (!session()->has('student_id')) {
if (!session()->has('student_id') && !session()->has('student_log_id')) {
return redirect('/signin')->with('error', 'Please login first!');
}
return view('StudentPortal');
})->name('student.portal');
// Profile Page (DB Dynamic Data)
Route::get('/StudentProfile', [StudentPortalController::class, 'showProfile'])->name('StudentProfile');
// Other Navigation Sub-pages
Route::get('/results', [ResultsController::class, 'index'])->name('results');
Route::get('/Dashboard', function () {
if (!session()->has('student_id')) { return redirect('/signin')->with('error', 'Please login first!'); }
if (!session()->has('student_id') && !session()->has('student_log_id')) { return redirect('/signin')->with('error', 'Please login first!'); }
return view('StudentPortal');
})->name('Dashboard');
Route::get('/mycourse', function () {
if (!session()->has('student_id')) { return redirect('/signin')->with('error', 'Please login first!'); }
if (!session()->has('student_id') && !session()->has('student_log_id')) { return redirect('/signin')->with('error', 'Please login first!'); }
return view('mycourse');
})->name('mycourse');
Route::get('/timetable', function () {
if (!session()->has('student_id')) { return redirect('/signin')->with('error', 'Please login first!'); }
return view('timetable');
})->name('timetable');
// 2. Dynamic Timetable Controller Route එක මෙතැනට යෙදුවා
Route::get('/timetable', [TimetableController::class, 'index'])->name('timetable');
Route::get('/Assignments', function () {
if (!session()->has('student_id')) { return redirect('/signin')->with('error', 'Please login first!'); }
if (!session()->has('student_id') && !session()->has('student_log_id')) { return redirect('/signin')->with('error', 'Please login first!'); }
return view('Assignments');
})->name('Assignments');
Route::get('/Studentguidelines', function () {
if (!session()->has('student_id')) { return redirect('/signin')->with('error', 'Please login first!'); }
if (!session()->has('student_id') && !session()->has('student_log_id')) { return redirect('/signin')->with('error', 'Please login first!'); }
return view('Studentguidelines');
})->name('Studentguidelines');
Route::get('/results', function () {
if (!session()->has('student_id')) { return redirect('/signin')->with('error', 'Please login first!'); }
return view('results');
})->name('results');
Route::get('/Feedback&Complain', [FeedbackController::class, 'index'])->name('feedback.index');
Route::post('/Feedback&Complain/store', [FeedbackController::class, 'store'])->name('feedback.store');