added leave request, profile and staff members controllers

This commit is contained in:
Kwesi Banson Jnr
2026-08-20 09:30:43 +00:00
parent 1c4d32833a
commit 81d26dc138
152 changed files with 1627 additions and 152101 deletions

View File

@@ -0,0 +1,143 @@
<?php
namespace App\Http\Controllers;
use App\Models\LeaveRequest;
use App\Models\StaffMember;
use Illuminate\Http\Request;
use Carbon\Carbon;
class LeaveRequestController extends Controller
{
public function index(Request $request)
{
$query = LeaveRequest::with('staff');
// Search by Staff Name
if ($request->filled('search')) {
$search = $request->search;
$query->whereHas('staff', function($q) use ($search) {
$q->where('name', 'like', "%{$search}%");
});
}
// Filter by Status
if ($request->filled('status')) {
$query->where('status', $request->status);
} else {
// Default to showing pending requests first if no filter is applied
$query->orderByRaw("FIELD(status, 'PENDING', 'APPROVED', 'REJECTED')");
}
$leaveRequests = $query->orderBy('created_at', 'desc')->paginate(15);
$leaveRequests->appends($request->query());
// KPIs for HR
$pendingCount = LeaveRequest::where('status', 'PENDING')->count();
$approvedThisMonth = LeaveRequest::where('status', 'APPROVED')
->whereMonth('start_date', Carbon::now()->month)
->whereYear('start_date', Carbon::now()->year)
->count();
$data = [
'leaveRequests' => $leaveRequests,
'pendingCount' => $pendingCount,
'approvedThisMonth' => $approvedThisMonth,
];
return view('leave.index', $data);
}
public function myLeave()
{
// Find the staff profile of the currently logged-in user
// Adjust this logic if you link Users to StaffMembers differently (e.g., via a staff_id foreign key)
$staff = StaffMember::where('email', auth()->user()->email)->firstOrFail();
// Fetch their leave history, latest first
$leaveRequests = LeaveRequest::where('staff_member_id', $staff->id)
->orderBy('created_at', 'desc')
->paginate(10);
$data = [
'staff' => $staff,
'leaveRequests' => $leaveRequests,
];
return view('leave.my_leave', $data);
}
public function store(Request $request)
{
$validated = $request->validate([
'staff_member_id' => 'required|exists:staff_members,id',
'leave_type' => 'required|string',
'start_date' => 'required|date',
'end_date' => 'required|date|after_or_equal:start_date',
'reason' => 'required|string',
]);
$start = Carbon::parse($validated['start_date']);
$end = Carbon::parse($validated['end_date']);
// Calculate total days excluding weekends
$totalDays = $start->diffInDaysFiltered(function (Carbon $date) {
return !$date->isWeekend();
}, $end) + 1; // +1 to include both the start and end day
if ($validated['leave_type'] === 'Annual') {
$staff = StaffMember::find($validated['staff_member_id']);
if ($staff->annual_leave_balance < $totalDays) {
return response()->json([
'success' => false,
'message' => "Insufficient balance. Requested: $totalDays days. Available: {$staff->annual_leave_balance} days."
], 422);
}
}
$validated['total_days'] = $totalDays;
$validated['status'] = 'PENDING';
LeaveRequest::create($validated);
return response()->json(['success' => true, 'message' => 'Leave request submitted successfully!']);
}
public function updateStatus(Request $request, $id)
{
$leaveRequest = LeaveRequest::findOrFail($id);
$validated = $request->validate([
'status' => 'required|in:APPROVED,REJECTED',
'admin_remarks' => 'nullable|string',
]);
if ($validated['status'] === 'APPROVED' && $leaveRequest->status !== 'APPROVED') {
if ($leaveRequest->leave_type === 'Annual') {
$staff = $leaveRequest->staff;
$staff->annual_leave_balance -= $leaveRequest->total_days;
$staff->save();
}
}
if ($validated['status'] === 'REJECTED' && $leaveRequest->status === 'APPROVED') {
if ($leaveRequest->leave_type === 'Annual') {
$staff = $leaveRequest->staff;
$staff->annual_leave_balance += $leaveRequest->total_days;
$staff->save();
}
}
$leaveRequest->update([
'status' => $validated['status'],
'admin_remarks' => $validated['admin_remarks'],
'approved_by' => auth()->id(), // Track who approved it
]);
return response()->json(['success' => true, 'message' => 'Leave status updated successfully!']);
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace App\Http\Controllers;
use App\Models\StaffMember;
use App\Models\StaffDependent;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class ProfileController extends Controller
{
public function index()
{
$staff = StaffMember::with('dependents')->where('email', auth()->user()->email)->firstOrFail();
return view('profile.index', compact('staff'));
}
public function update(Request $request)
{
$staff = StaffMember::where('email', auth()->user()->email)->firstOrFail();
$validated = $request->validate([
'name' => 'required|string|max:200',
'phone' => 'nullable|string|max:200',
'personal_email' => 'nullable|email|max:191',
'location_country' => 'nullable|string|max:191',
'birth_month' => 'nullable|string|size:2',
'birth_day' => 'nullable|string|size:2',
'photo' => 'nullable|image|mimes:jpg,jpeg,png|max:2048',
]);
// Combine month and day if both are provided (e.g., "12-25")
if (!empty($request->birth_month) && !empty($request->birth_day)) {
$validated['dob'] = $request->birth_month . '-' . $request->birth_day;
} else {
$validated['dob'] = null;
}
// Clean up temporary keys
unset($validated['birth_month'], $validated['birth_day'], $validated['photo']);
if ($request->hasFile('photo')) {
if ($staff->profile_pic && Storage::disk('public')->exists($staff->profile_pic)) {
Storage::disk('public')->delete($staff->profile_pic);
}
$validated['profile_pic'] = $request->file('photo')->store('staff_profiles', 'public');
}
unset($validated['photo']);
$staff->update($validated);
return response()->json(['success' => true, 'message' => 'Profile updated successfully!']);
}
// --- DEPENDENTS / EMERGENCY CONTACTS CRUD ---
public function storeDependent(Request $request)
{
$staff = StaffMember::where('email', auth()->user()->email)->firstOrFail();
$validated = $request->validate([
'fullname' => 'required|string|max:255',
'relationship' => 'required|string|max:192',
'phone' => 'nullable|string|max:20',
'medical_details' => 'nullable|string',
]);
$validated['staff_id'] = $staff->id;
StaffDependent::create($validated);
return response()->json(['success' => true, 'message' => 'Emergency contact added successfully!']);
}
public function updateDependent(Request $request, $id)
{
$staff = StaffMember::where('email', auth()->user()->email)->firstOrFail();
$dependent = StaffDependent::where('staff_id', $staff->id)->findOrFail($id);
$validated = $request->validate([
'fullname' => 'required|string|max:255',
'relationship' => 'required|string|max:192',
'phone' => 'nullable|string|max:20',
'medical_details' => 'nullable|string',
]);
$dependent->update($validated);
return response()->json(['success' => true, 'message' => 'Emergency contact updated successfully!']);
}
public function destroyDependent($id)
{
$staff = StaffMember::where('email', auth()->user()->email)->firstOrFail();
$dependent = StaffDependent::where('staff_id', $staff->id)->findOrFail($id);
$dependent->delete();
return redirect()->back()->with('success', 'Emergency contact removed.');
}
}

View File

@@ -1,16 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class StaffController extends Controller
{
public function index()
{
$data = [
'page_title' => 'Dashboard'
];
return view('staff.index', $data);
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace App\Http\Controllers;
use App\Models\StaffMember;
use App\Models\Department;
use App\Models\Country;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Carbon\Carbon;
class StaffMembersController extends Controller
{
public function index(Request $request)
{
$query = StaffMember::query();
// 1. Search Filter
if ($request->filled('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%")
->orWhere('designation', 'like', "%{$search}%")
->orWhere('phone', 'like', "%{$search}%");
});
}
// 2. Department Filter
if ($request->filled('department_id')) {
$query->where('department_id', $request->department_id);
}
// 3. Status Filter
if ($request->filled('status')) {
$query->where('status', $request->status);
}
$staffMembers = $query->orderBy('name', 'asc')->paginate(12);
$staffMembers->appends($request->query());
// Fetch dynamic dropdown lists
$departments = Department::orderBy('name', 'asc')->get();
$countries = Country::orderBy('en_short_name', 'asc')->get();
$data = [
'staffMembers' => $staffMembers,
'departments' => $departments,
'countries' => $countries,
];
return view('staff.index', $data);
}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:200',
'designation' => 'required|string|max:191',
'email' => 'required|email|max:200',
'phone' => 'nullable|string|max:200',
'department_id' => 'required|integer',
'location_country' => 'nullable|string|max:191',
'status' => 'required|string|max:20',
'photo' => 'nullable|image|mimes:jpg,jpeg,png|max:2048',
]);
if ($request->hasFile('photo')) {
$validated['profile_pic'] = $request->file('photo')->store('staff_profiles', 'public');
}
$validated['created_by'] = auth()->id() ?? 1;
$validated['password'] = Hash::make('default_password');
$validated['hire_date'] = now();
StaffMember::create($validated);
return response()->json(['success' => true, 'message' => 'Staff member added successfully!']);
}
public function update(Request $request, $id)
{
$staff = StaffMember::findOrFail($id);
$validated = $request->validate([
'name' => 'required|string|max:200',
'designation' => 'required|string|max:191',
'email' => 'required|email|max:200',
'phone' => 'nullable|string|max:200',
'department_id' => 'required|integer',
'location_country' => 'nullable|string|max:191',
'status' => 'required|string|max:20',
'photo' => 'nullable|image|mimes:jpg,jpeg,png|max:2048',
]);
if ($request->hasFile('photo')) {
if ($staff->profile_pic && Storage::disk('public')->exists($staff->profile_pic)) {
Storage::disk('public')->delete($staff->profile_pic);
}
$validated['profile_pic'] = $request->file('photo')->store('staff_profiles', 'public');
}
$validated['modified_by'] = auth()->id() ?? 1;
$staff->update($validated);
return response()->json(['success' => true, 'message' => 'Staff member updated successfully!']);
}
public function destroy($id)
{
$staff = StaffMember::findOrFail($id);
if ($staff->profile_pic && Storage::disk('public')->exists($staff->profile_pic)) {
Storage::disk('public')->delete($staff->profile_pic);
}
$staff->delete();
return redirect()->back()->with('success', 'Staff member removed.');
}
public function getUpcomingBirthdays()
{
$today = Carbon::now();
$thirtyDaysFromNow = Carbon::now()->addDays(30);
$currentMonthDay = $today->format('m-d');
$futureMonthDay = $thirtyDaysFromNow->format('m-d');
if ($currentMonthDay <= $futureMonthDay) {
// Simple range check within the same calendar year
$upcomingStaff = StaffMember::whereBetween('dob', [$currentMonthDay, $futureMonthDay])
->orderBy('dob', 'asc')
->get();
} else {
// Handles the year-end crossover (e.g., looking from December into January)
$upcomingStaff = StaffMember::where(function($query) use ($currentMonthDay, $futureMonthDay) {
$query->where('dob', '>=', $currentMonthDay)
->orWhere('dob', '<=', $futureMonthDay);
})
->orderBy('dob', 'asc')
->get();
}
return $upcomingStaff;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class LeaveRequest extends Model
{
protected $casts = [
'start_date' => 'date',
'end_date' => 'date',
];
protected $guarded = ['id'];
public function staff()
{
return $this->belongsTo(StaffMember::class, 'staff_member_id');
}
// In StaffMember.php
public function leaveRequests()
{
return $this->hasMany(LeaveRequest::class, 'staff_member_id');
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class StaffDependent extends Model
{
protected $table = 'staff_dependents';
protected $guarded = [];
public function staff()
{
return $this->belongsTo(StaffMember::class, 'staff_id');
}
}

View File

@@ -21,9 +21,16 @@ class StaffMember extends Authenticatable
'password',
];
public function leaveRequests() {
return $this->hasMany(LeaveRequest::class, 'staff_member_id');
}
public function dependents() {
return $this->hasMany(StaffDependent::class, 'staff_id');
}
// 4. Cast dates correctly
protected $casts = [
'dob' => 'date',
// 'dob' => 'date',
'hire_date' => 'datetime',
'password' => 'hashed', // Laravel 10+ password casting
];