taklimat ulangan+profil
This commit is contained in:
@@ -59,6 +59,23 @@ class CheckinController extends Controller
|
||||
])->onlyInput('no_kp');
|
||||
}
|
||||
|
||||
// Pernah hadir di sesi taklimat LAIN? Taklimat ulangan hanya untuk
|
||||
// petugas yang belum pernah hadir langsung — tak perlu rekod semula.
|
||||
$rekodSesiLain = KehadiranTaklimat::where('permohonan_id', $petugas->id)
|
||||
->where('sesi_taklimat_id', '!=', $sesi->id)
|
||||
->with('sesi.dun')
|
||||
->orderBy('hadir_at')
|
||||
->first();
|
||||
|
||||
if ($rekodSesiLain) {
|
||||
return view('checkin.pernah-hadir', [
|
||||
'sesi' => $sesi,
|
||||
'petugas' => $petugas,
|
||||
'kehadiranSebelumIni' => $rekodSesiLain,
|
||||
'sesiSebelumIni' => $rekodSesiLain->sesi,
|
||||
]);
|
||||
}
|
||||
|
||||
// Jika sudah daftar dalam sesi ini, papar semula slip sedia ada
|
||||
$kehadiran = KehadiranTaklimat::where('sesi_taklimat_id', $sesi->id)
|
||||
->where('permohonan_id', $petugas->id)
|
||||
|
||||
@@ -66,7 +66,7 @@ class KaunterController extends Controller
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if ($user->isPengurus() && (int) $sesi->dun_id !== (int) $user->dun_id) {
|
||||
if (! $user->isAdmin() && (int) $sesi->dun_id !== (int) $user->dun_id) {
|
||||
abort(403, 'Sesi ini bukan untuk DUN anda.');
|
||||
}
|
||||
}
|
||||
|
||||
67
app/Http/Controllers/PasswordResetController.php
Normal file
67
app/Http/Controllers/PasswordResetController.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
|
||||
class PasswordResetController extends Controller
|
||||
{
|
||||
public function showLinkRequestForm()
|
||||
{
|
||||
return view('auth.lupa-katalaluan');
|
||||
}
|
||||
|
||||
public function sendResetLink(Request $request)
|
||||
{
|
||||
$request->validate(['email' => ['required', 'email']]);
|
||||
|
||||
$status = Password::sendResetLink($request->only('email'));
|
||||
|
||||
return $status === Password::RESET_LINK_SENT
|
||||
? back()->with('status', 'Pautan set semula kata laluan telah dihantar ke e-mel anda.')
|
||||
: back()->withErrors(['email' => $this->mesejRalat($status)]);
|
||||
}
|
||||
|
||||
public function showResetForm(Request $request, string $token)
|
||||
{
|
||||
return view('auth.set-kata-laluan', [
|
||||
'token' => $token,
|
||||
'email' => $request->query('email', ''),
|
||||
]);
|
||||
}
|
||||
|
||||
public function reset(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'token' => ['required', 'string'],
|
||||
'email' => ['required', 'email'],
|
||||
'password' => ['required', 'string', 'min:8', 'confirmed'],
|
||||
], [
|
||||
'password.min' => 'Kata laluan perlu sekurang-kurangnya 8 aksara.',
|
||||
'password.confirmed' => 'Pengesahan kata laluan tidak sepadan.',
|
||||
]);
|
||||
|
||||
$status = Password::reset(
|
||||
$data,
|
||||
function ($user, $password) {
|
||||
$user->forceFill(['password' => Hash::make($password)])->save();
|
||||
}
|
||||
);
|
||||
|
||||
return $status === Password::PASSWORD_RESET
|
||||
? redirect()->route('login')->with('status', 'Kata laluan berjaya ditetapkan semula. Sila log masuk.')
|
||||
: back()->withErrors(['email' => $this->mesejRalat($status)])->onlyInput('email');
|
||||
}
|
||||
|
||||
private function mesejRalat(string $status): string
|
||||
{
|
||||
return match ($status) {
|
||||
Password::INVALID_USER => 'Akaun dengan e-mel ini tidak ditemui.',
|
||||
Password::RESET_THROTTLED => 'Sila tunggu sebentar sebelum cuba lagi.',
|
||||
Password::INVALID_TOKEN => 'Pautan set semula tidak sah atau telah tamat tempoh.',
|
||||
default => 'Gagal memproses permintaan. Sila cuba lagi.',
|
||||
};
|
||||
}
|
||||
}
|
||||
94
app/Http/Controllers/PenggunaController.php
Normal file
94
app/Http/Controllers/PenggunaController.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Sppm\Dun;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PenggunaController extends Controller
|
||||
{
|
||||
/**
|
||||
* Peranan yang boleh didaftarkan oleh pengurus DUN (untuk DUN sendiri sahaja).
|
||||
*/
|
||||
private const PERANAN_PENGURUS = ['pengurus', 'kaunter'];
|
||||
|
||||
private const PERANAN_ADMIN = ['admin', 'pengurus', 'kaunter'];
|
||||
|
||||
/**
|
||||
* Senarai pengguna. Pengurus hanya nampak pengguna DUN sendiri.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$pengguna = User::query()
|
||||
->when(
|
||||
$user->isPengurus(),
|
||||
fn ($q) => $q->where('dun_id', $user->dun_id)->whereIn('role', self::PERANAN_PENGURUS)
|
||||
)
|
||||
->with('dun')
|
||||
->orderBy('role')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
return view('pengguna.index', compact('pengguna'));
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$senaraiDun = Dun::where('is_active', true)
|
||||
->when($user->isPengurus(), fn ($q) => $q->where('id', $user->dun_id))
|
||||
->orderBy('code')
|
||||
->get();
|
||||
|
||||
$peranan = $user->isAdmin() ? self::PERANAN_ADMIN : self::PERANAN_PENGURUS;
|
||||
|
||||
return view('pengguna.create', compact('senaraiDun', 'peranan'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
$perananDibenarkan = $user->isAdmin() ? self::PERANAN_ADMIN : self::PERANAN_PENGURUS;
|
||||
|
||||
$data = $request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
|
||||
'role' => ['required', 'string', Rule::in($perananDibenarkan)],
|
||||
'dun_id' => ['nullable', 'integer'],
|
||||
'password' => ['required', 'string', 'min:8', 'confirmed'],
|
||||
], [
|
||||
'email.unique' => 'E-mel ini sudah didaftarkan.',
|
||||
'password.min' => 'Kata laluan perlu sekurang-kurangnya 8 aksara.',
|
||||
'password.confirmed' => 'Pengesahan kata laluan tidak sepadan.',
|
||||
]);
|
||||
|
||||
if ($user->isPengurus()) {
|
||||
// Pengurus hanya boleh daftar untuk DUN sendiri, tidak kira input.
|
||||
$data['dun_id'] = $user->dun_id;
|
||||
} elseif ($data['role'] === 'admin') {
|
||||
$data['dun_id'] = null;
|
||||
} elseif (empty($data['dun_id'])) {
|
||||
return back()->withErrors(['dun_id' => 'Sila pilih DUN untuk peranan ini.'])->withInput();
|
||||
}
|
||||
|
||||
if (! empty($data['dun_id']) && ! Dun::where('id', $data['dun_id'])->where('is_active', true)->exists()) {
|
||||
return back()->withErrors(['dun_id' => 'DUN tidak sah.'])->withInput();
|
||||
}
|
||||
|
||||
User::create([
|
||||
'name' => $data['name'],
|
||||
'email' => $data['email'],
|
||||
'role' => $data['role'],
|
||||
'dun_id' => $data['dun_id'] ?? null,
|
||||
'password' => Hash::make($data['password']),
|
||||
]);
|
||||
|
||||
return redirect()->route('pengguna.index')->with('status', 'Pengguna baharu berjaya didaftarkan.');
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\KehadiranTaklimat;
|
||||
use App\Models\SesiTaklimat;
|
||||
use App\Models\Sppm\PetugasPermohonan;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -9,13 +10,15 @@ use Illuminate\Http\Request;
|
||||
class PengurusController extends Controller
|
||||
{
|
||||
/**
|
||||
* Senarai petugas hadir dan tidak/belum hadir bagi satu sesi taklimat.
|
||||
* Senarai petugas bagi satu sesi taklimat, dipecahkan kepada 3 kumpulan:
|
||||
* hadir (sesi ini), pernah hadir (sesi taklimat lain), dan
|
||||
* tidak/belum hadir langsung (tidak pernah hadir mana-mana sesi).
|
||||
*/
|
||||
public function senarai(Request $request, SesiTaklimat $sesi)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if ($user->isPengurus() && (int) $sesi->dun_id !== (int) $user->dun_id) {
|
||||
if (! $user->isAdmin() && (int) $sesi->dun_id !== (int) $user->dun_id) {
|
||||
abort(403, 'Sesi ini bukan untuk DUN anda.');
|
||||
}
|
||||
|
||||
@@ -29,15 +32,28 @@ class PengurusController extends Controller
|
||||
|
||||
$kehadiran = $sesi->kehadiran()->get()->keyBy('permohonan_id');
|
||||
|
||||
[$hadir, $tidakHadir] = $semuaPetugas->partition(
|
||||
fn (PetugasPermohonan $p) => $kehadiran->has($p->id)
|
||||
$belumHadirSesiIni = $semuaPetugas->reject(fn (PetugasPermohonan $p) => $kehadiran->has($p->id));
|
||||
|
||||
// Antara yang belum hadir sesi ini, semak siapa pernah hadir di sesi LAIN.
|
||||
$kehadiranSesiLain = KehadiranTaklimat::whereIn('permohonan_id', $belumHadirSesiIni->pluck('id'))
|
||||
->where('sesi_taklimat_id', '!=', $sesi->id)
|
||||
->with('sesi.dun')
|
||||
->orderBy('hadir_at')
|
||||
->get()
|
||||
->unique('permohonan_id')
|
||||
->keyBy('permohonan_id');
|
||||
|
||||
[$hadirSesiLain, $tidakHadir] = $belumHadirSesiIni->partition(
|
||||
fn (PetugasPermohonan $p) => $kehadiranSesiLain->has($p->id)
|
||||
);
|
||||
|
||||
return view('pengurus.senarai', [
|
||||
'sesi' => $sesi,
|
||||
'hadir' => $hadir->values(),
|
||||
'hadir' => $semuaPetugas->filter(fn (PetugasPermohonan $p) => $kehadiran->has($p->id))->values(),
|
||||
'hadirSesiLain' => $hadirSesiLain->values(),
|
||||
'tidakHadir' => $tidakHadir->values(),
|
||||
'kehadiran' => $kehadiran,
|
||||
'kehadiranSesiLain' => $kehadiranSesiLain,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
37
app/Http/Controllers/ProfilController.php
Normal file
37
app/Http/Controllers/ProfilController.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class ProfilController extends Controller
|
||||
{
|
||||
public function edit(Request $request)
|
||||
{
|
||||
return view('profil.edit', ['pengguna' => $request->user()]);
|
||||
}
|
||||
|
||||
public function updatePassword(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'kata_laluan_semasa' => ['required', 'string'],
|
||||
'kata_laluan_baharu' => ['required', 'string', 'min:8', 'confirmed'],
|
||||
], [
|
||||
'kata_laluan_semasa.required' => 'Sila masukkan kata laluan semasa.',
|
||||
'kata_laluan_baharu.required' => 'Sila masukkan kata laluan baharu.',
|
||||
'kata_laluan_baharu.min' => 'Kata laluan baharu perlu sekurang-kurangnya 8 aksara.',
|
||||
'kata_laluan_baharu.confirmed' => 'Pengesahan kata laluan baharu tidak sepadan.',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
if (! Hash::check($data['kata_laluan_semasa'], $user->password)) {
|
||||
return back()->withErrors(['kata_laluan_semasa' => 'Kata laluan semasa tidak tepat.']);
|
||||
}
|
||||
|
||||
$user->update(['password' => Hash::make($data['kata_laluan_baharu'])]);
|
||||
|
||||
return back()->with('status', 'Kata laluan berjaya dikemaskini.');
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,14 @@ use Illuminate\Http\Request;
|
||||
class SesiTaklimatController extends Controller
|
||||
{
|
||||
/**
|
||||
* Senarai sesi taklimat. Pengurus hanya nampak DUN sendiri.
|
||||
* Senarai sesi taklimat. Pengurus & kaunter hanya nampak DUN sendiri.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$sesi = SesiTaklimat::withCount('kehadiran')
|
||||
->when($user->isPengurus(), fn ($q) => $q->where('dun_id', $user->dun_id))
|
||||
->when(! $user->isAdmin(), fn ($q) => $q->where('dun_id', $user->dun_id))
|
||||
->orderByDesc('tarikh')
|
||||
->get();
|
||||
|
||||
@@ -106,7 +106,7 @@ class SesiTaklimatController extends Controller
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if ($user->isPengurus() && (int) $sesi->dun_id !== (int) $user->dun_id) {
|
||||
if (! $user->isAdmin() && (int) $sesi->dun_id !== (int) $user->dun_id) {
|
||||
abort(403, 'Sesi ini bukan untuk DUN anda.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use App\Models\Sppm\Dun;
|
||||
use App\Notifications\ResetKataLaluanNotification;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
@@ -27,6 +30,24 @@ class User extends Authenticatable
|
||||
return $this->role === 'pengurus';
|
||||
}
|
||||
|
||||
public function isKaunter(): bool
|
||||
{
|
||||
return $this->role === 'kaunter';
|
||||
}
|
||||
|
||||
public function dun(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Dun::class, 'dun_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Guna e-mel dalam Bahasa Melayu untuk pautan set semula kata laluan.
|
||||
*/
|
||||
public function sendPasswordResetNotification(#[\SensitiveParameter] $token): void
|
||||
{
|
||||
$this->notify(new ResetKataLaluanNotification($token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
|
||||
38
app/Notifications/ResetKataLaluanNotification.php
Normal file
38
app/Notifications/ResetKataLaluanNotification.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class ResetKataLaluanNotification extends Notification
|
||||
{
|
||||
public function __construct(private readonly string $token)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int,string>
|
||||
*/
|
||||
public function via(mixed $notifiable): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
public function toMail(mixed $notifiable): MailMessage
|
||||
{
|
||||
$url = url(route('password.reset', [
|
||||
'token' => $this->token,
|
||||
'email' => $notifiable->getEmailForPasswordReset(),
|
||||
], false));
|
||||
|
||||
$minit = config('auth.passwords.users.expire');
|
||||
|
||||
return (new MailMessage)
|
||||
->subject('Set Semula Kata Laluan - Kehadiran Taklimat SPR')
|
||||
->line('Kami menerima permintaan untuk set semula kata laluan akaun anda.')
|
||||
->action('Set Semula Kata Laluan', $url)
|
||||
->line("Pautan ini akan tamat tempoh dalam {$minit} minit.")
|
||||
->line('Jika anda tidak membuat permintaan ini, sila abaikan e-mel ini.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user