Compare commits

..

27 Commits

Author SHA1 Message Date
ell 635d44a0fb Merge branch 'main' of https://git-jtipolije.com/afiahh/MIF_31230115 2026-06-25 11:02:06 +07:00
ell e886d09193 manajemen mp 2026-06-25 11:01:38 +07:00
ell 1f9a350251 finish 2026-06-09 21:05:50 +07:00
ell 54a4a3a44b multi role + kolat 2026-06-08 12:16:32 +07:00
SitiAfiah e748c8cb60 Merge branch 'main' of https://github.com/SitiAfiah/Project-TA 2026-06-07 20:21:08 +07:00
SitiAfiah 6307484ffe penialain pelatih. 2026-06-07 19:58:21 +07:00
ell 43083a72b9 Merge branch 'main' of https://github.com/SitiAfiah/Project-TA 2026-06-06 09:22:21 +07:00
ell 4b82871a9f latest anggota 2026-06-06 09:22:03 +07:00
SitiAfiah 9c68d4b734 genarate spp serta update anggota 2026-06-05 19:03:03 +07:00
SitiAfiah 06f672b441 ubah layout 2026-05-19 19:12:08 +07:00
SitiAfiah 2ccaa0b4c5 rekap dan dashboard 2026-05-06 20:55:12 +07:00
SitiAfiah 7027bdd847 Merge branch 'main' of https://github.com/SitiAfiah/Project-TA 2026-05-04 00:43:39 +07:00
SitiAfiah 723a179871 edit pelatih 2026-05-04 00:38:18 +07:00
ell 55bed66705 laporan 2026-05-04 00:36:52 +07:00
ell af264cb030 laporan 2026-05-04 00:34:02 +07:00
SitiAfiah b73d74ebee Merge branch 'main' of https://github.com/SitiAfiah/Project-TA 2026-05-03 16:02:06 +07:00
SitiAfiah f7db432276 kas, spp, penialaian 2026-05-03 14:41:36 +07:00
ell 50a5d55169 presensi 2026-04-30 17:40:44 +07:00
ell 03fa985785 sidebar + middleware 2026-04-27 20:33:58 +07:00
ell e28efe700b login + register, update view anggota 2026-04-27 19:50:36 +07:00
SitiAfiah 65bc2458ac manajemen latihan belum 2026-04-26 20:05:32 +07:00
SitiAfiah 2092a810ce pengumuman (masih eror) 2026-04-20 19:10:44 +07:00
SitiAfiah a53eefa96e kolat, dan anggota 2026-04-10 21:49:19 +07:00
SitiAfiah 75cfd09205 sidebar dan table anggota 2026-03-29 23:23:36 +07:00
SitiAfiah c5b4e84f78 landing page minus 2026-03-07 17:15:07 +07:00
SitiAfiah 9a8bffba10 dashboard 2026-02-23 11:32:22 +07:00
SitiAfiah 492d58a0a7 setup 2026-02-23 10:15:26 +07:00
791 changed files with 773638 additions and 0 deletions

View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

59
manajemen_mp/.env.example Normal file
View File

@ -0,0 +1,59 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

11
manajemen_mp/.gitattributes vendored Normal file
View File

@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

19
manajemen_mp/.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
/.phpunit.cache
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
/.fleet
/.idea
/.vscode

66
manajemen_mp/README.md Normal file
View File

@ -0,0 +1,66 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
### Premium Partners
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[WebReinvent](https://webreinvent.com/)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
- **[Cyber-Duck](https://cyber-duck.co.uk)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Jump24](https://jump24.co.uk)**
- **[Redberry](https://redberry.international/laravel/)**
- **[Active Logic](https://activelogic.com)**
- **[byte5](https://byte5.de)**
- **[OP.GG](https://op.gg)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View File

@ -0,0 +1,27 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*/
protected function schedule(Schedule $schedule): void
{
// $schedule->command('inspire')->hourly();
}
/**
* Register the commands for the application.
*/
protected function commands(): void
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* The list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*/
public function register(): void
{
$this->reportable(function (Throwable $e) {
//
});
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Exports;
use App\Models\Anggota;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
class AnggotaExport implements FromCollection, WithHeadings, WithMapping, ShouldAutoSize
{
public function collection()
{
// Ambil data Anggota dengan relasi kolat dan user (untuk email)
return Anggota::whereHas('roles', function($query) {
$query->where('nama_role', 'Anggota');
})->with(['kolat', 'user'])->get();
}
public function headings(): array
{
return [
'No Induk',
'Nama Lengkap',
'Email',
'JK',
'Tempat Lahir',
'Tanggal Lahir',
'No HP',
'Kolat',
'Tingkatan',
'Jabatan',
'Tanggal Gabung',
'Status',
'Alamat',
'Catatan Medis'
];
}
public function map($anggota): array
{
return [
$anggota->no_induk,
$anggota->nama_lengkap,
$anggota->user->email ?? '-',
$anggota->jenis_kelamin,
$anggota->tempat_lahir,
$anggota->tgl_lahir,
$anggota->no_hp,
$anggota->kolat->nama_kolat ?? '-',
$anggota->tingkatan,
$anggota->jabatan,
$anggota->tgl_gabung,
$anggota->status,
$anggota->alamat,
$anggota->catatan_medis ?? '-'
];
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Exports;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
class KasExport implements FromCollection, WithHeadings, WithMapping, ShouldAutoSize
{
protected $data;
// Kita terima data dari Controller lewat constructor
public function __construct($data)
{
$this->data = $data;
}
public function collection()
{
return $this->data;
}
public function headings(): array
{
return [
'Tanggal',
'Jenis',
'Kategori',
'Nominal',
'Keterangan',
'Saldo Akhir'
];
}
public function map($kas): array
{
return [
\Carbon\Carbon::parse($kas->tanggal)->format('d/m/Y'),
ucfirst($kas->jenis),
$kas->kategori,
$kas->nominal,
$kas->keterangan ?? '-',
$kas->saldo_akhir
];
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Exports;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
class PresensiExport implements FromCollection, WithHeadings, WithMapping, ShouldAutoSize
{
protected $data;
public function __construct($data)
{
$this->data = $data;
}
public function collection()
{
return $this->data;
}
public function headings(): array
{
return [
'No',
'Nama Anggota',
'Kolat',
'Tanggal Latihan',
'Jam',
'Pelatih (Jadwal)',
'Status Kehadiran',
'Waktu Absen',
'Verifikator',
'Keterangan'
];
}
public function map($p): array
{
static $no = 0;
$no++;
return [
$no,
$p->anggota->nama_lengkap ?? '-',
$p->jadwal->kolat->nama_kolat ?? '-',
\Carbon\Carbon::parse($p->jadwal->tanggal)->format('d/m/Y'),
($p->jadwal->jam_mulai ?? '-') . ' - ' . ($p->jadwal->jam_selesai ?? '-'),
$p->jadwal->pelatih->nama_lengkap ?? '-',
$p->status,
$p->waktu_presensi ? \Carbon\Carbon::parse($p->waktu_presensi)->format('H:i') . ' WIB' : '-',
$p->pelatihVerifikator->nama_lengkap ?? 'Otomatis/Sistem',
$p->keterangan ?? '-'
];
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Exports;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
class SppExport implements FromCollection, WithHeadings, WithMapping, ShouldAutoSize
{
protected $data;
public function __construct($data)
{
$this->data = $data;
}
public function collection()
{
return $this->data;
}
public function headings(): array
{
return [
'Nama Anggota',
'Kolat',
'Bulan',
'Tahun',
'Nominal (Rp)',
'Jatuh Tempo',
'Status',
'Keterangan'
];
}
public function map($spp): array
{
return [
$spp->anggota->nama_lengkap ?? '-', // Menggunakan nama_lengkap sesuai model Anggota
$spp->kolat->nama_kolat ?? '-',
$spp->bulan,
$spp->tahun,
$spp->nominal,
\Carbon\Carbon::parse($spp->jatuh_tempo)->format('d/m/Y'),
strtoupper(str_replace('_', ' ', $spp->status)), // Misal: "belum_bayar" jadi "BELUM BAYAR"
$spp->keterangan ?? '-'
];
}
}

View File

@ -0,0 +1,184 @@
<?php
namespace App\Http\Controllers;
use App\Exports\AnggotaExport;
use App\Models\Anggota;
use App\Models\Kolat; // Import model Kolat
use App\Models\Role;
use App\Models\User;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Facades\Excel;
class AnggotaController extends Controller
{
// 1. Menampilkan Daftar Anggota
public function anggota()
{
// Filter data: Ambil anggota yang relasi 'role'-nya bernama 'Anggota'
$data_anggota = Anggota::whereHas('roles', function($query) {
$query->where('nama_role', 'Anggota');
})->with(['kolat', 'roles'])
->latest()
->get();
return view('anggota.anggota', compact('data_anggota'));
}
// 2. Menampilkan Form Tambah
public function create()
{
// Ambil semua daftar kolat untuk ditampilkan di dropdown form
$data_kolat = Kolat::all();
$data_role = Role::all();
return view('anggota.anggotacreate', compact('data_kolat', 'data_role'));
}
public function store(Request $request)
{
$request->validate([
'email' => 'required|email|unique:users,email',
'nama_lengkap' => 'required',
'no_hp' => 'required',
'role_id' => 'required|exists:role,id',
'kolat_id' => 'required|exists:kolat,id',
'jenis_kelamin' => 'required',
'tempat_lahir' => 'required',
'tgl_lahir' => 'required|date',
'tingkatan' => 'required',
'tgl_gabung' => 'required|date',
]);
DB::beginTransaction();
try {
// A. Buat Akun Login di tabel Users
$user = User::create([
'email' => $request->email,
'password' => Hash::make('tapakmp123'), // Password default
]);
// B. Cari nama jabatan berdasarkan role
$role = Role::find($request->role_id);
$nama_jabatan = $role ? strtolower($role->nama_role) : 'anggota';
// C. Logika No Induk (JB-001)
$terakhir = Anggota::orderBy('id', 'desc')->first();
$nomor_urut = $terakhir ? sprintf("%03d", intval(substr($terakhir->no_induk, 3)) + 1) : "001";
$no_induk_baru = "JB-" . $nomor_urut;
// D. Simpan Profil di tabel Anggotas
$anggota = Anggota::create([
'user_id' => $user->id,
'no_induk' => $no_induk_baru,
'nama_lengkap' => $request->nama_lengkap,
'jenis_kelamin' => $request->jenis_kelamin,
'tempat_lahir' => $request->tempat_lahir,
'tgl_lahir' => $request->tgl_lahir,
'no_hp' => $request->no_hp,
'kolat_id' => $request->kolat_id,
'tingkatan' => $request->tingkatan,
'tgl_gabung' => $request->tgl_gabung,
'alamat' => $request->alamat,
'catatan_medis' => $request->catatan_medis,
'jabatan' => $nama_jabatan,
'status' => 'Aktif', // Input admin otomatis aktif
]);
$anggota->roles()->attach($request->role_id);
DB::commit();
return redirect()->route('anggota.anggota')
->with('success', 'Data berhasil ditambah! Password login: tapakmp123');
} catch (\Exception $e) {
DB::rollBack();
Log::error("Gagal tambah anggota: " . $e->getMessage());
return back()->withInput()->with('error', 'Terjadi kesalahan sistem.');
}
}
// 4. Menampilkan Form Edit
public function edit($id)
{
$anggota = Anggota::with(['user', 'roles'])->findOrFail($id);
$data_kolat = Kolat::all(); // Ambil daftar kolat lagi untuk pilihan saat edit
$data_role = Role::all();
return view('anggota.editanggota', compact('anggota', 'data_kolat', 'data_role'));
}
// 5. Update Data Anggota
public function update(Request $request, $id)
{
$anggota = Anggota::findOrFail($id);
$user = User::findOrFail($anggota->user_id);
$request->validate([
'email' => 'required|email|unique:users,email,' . $user->id,
'nama_lengkap' => 'required',
'no_hp' => 'required',
'role_id' => 'required|exists:role,id',
'kolat_id' => 'required|exists:kolat,id',
'status' => 'required|in:Aktif,Non-Aktif',
]);
DB::beginTransaction();
try {
// A. Update tabel User (email & role)
$user->update([
'email' => $request->email,
]);
// B. Update jabatan berdasarkan role baru
$role = Role::find($request->role_id);
$nama_jabatan = $role ? strtolower($role->nama_role) : 'anggota';
// C. Update tabel Anggota
// $data_anggota = $request->all();
$data_anggota = $request->except(['email', 'role_id', '_token', '_method']);
$data_anggota['jabatan'] = $nama_jabatan;
$anggota->update($data_anggota);
$anggota->roles()->sync([$request->role_id]);
DB::commit();
return redirect()->route('anggota.anggota')
->with('success', 'Data ' . $anggota->nama_lengkap . ' berhasil diperbarui!');
} catch (\Exception $e) {
DB::rollBack();
Log::error("Gagal update anggota: " . $e->getMessage());
return back()->with('error', 'Terjadi kesalahan saat memperbarui data.');
}
}
public function exportExcel()
{
return Excel::download(new AnggotaExport, 'Data_Anggota_TapakMP.xlsx');
}
public function exportPdf()
{
$data_anggota = Anggota::whereHas('roles', function($query) {
$query->where('nama_role', 'Anggota');
})->with(['kolat', 'user'])->get();
// Menggunakan view khusus PDF
$pdf = Pdf::loadView('anggota.export_pdf', compact('data_anggota'));
// Karena kolomnya banyak, set ke Landscape agar tidak berdesakan
$pdf->setPaper('a4', 'potrait');
return $pdf->download('Laporan_Anggota_TapakMP.pdf');
}
}

View File

@ -0,0 +1,183 @@
<?php
namespace App\Http\Controllers;
use App\Models\Anggota;
use App\Models\Kolat;
use App\Models\Role;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
class AuthController extends Controller
{
/**
* Menampilkan Halaman Login
*/
public function showLogin()
{
return view('login-register.login');
}
/**
* Proses Login
*/
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (Auth::attempt($credentials)) {
$user = Auth::user();
// 1. Proteksi Status: Jika belum diaktifkan admin, tidak boleh masuk
if ($user->anggota && $user->anggota->status !== 'Aktif') {
Auth::logout();
return back()->with('error', 'Akun Anda sedang dalam tahap verifikasi admin. Mohon tunggu.');
}
$request->session()->regenerate();
// 2. Ambil Nama Role (samakan logikanya dengan Middleware)
// $roleName = null;
// if ($user->role) {
// $roleName = $user->role->nama_role;
// } elseif ($user->anggota && $user->anggota->role) {
// $roleName = $user->anggota->role->nama_role;
// }
$roleName = null;
$anggota = $user->anggota;
if ($anggota) {
// Cek apakah dia punya role Pengurus
if ($anggota->roles->contains('nama_role', 'Pengurus')) {
$roleName = 'Pengurus';
}
// Kalau bukan Pengurus, cek apakah dia Pelatih
elseif ($anggota->roles->contains('nama_role', 'Pelatih')) {
$roleName = 'Pelatih';
}
// Kalau bukan keduanya, berarti Anggota biasa
elseif ($anggota->roles->contains('nama_role', 'Anggota')) {
$roleName = 'Anggota';
}
}
// 3. Logika Pengarahan (Redirect) berdasarkan String Role
if ($roleName === 'Anggota') {
return redirect()->route('anggota.dashboard')->with('success', 'Selamat datang, Anggota!');
}
if ($roleName === 'Pelatih' || $roleName === 'Pengurus') {
return redirect()->route('dashboard')->with('success', 'Selamat datang, Pelatih/Pengurus!');
}
// Default redirect jika entah kenapa role kosong
return redirect()->intended('/dashboard');
}
return back()->withErrors([
'email' => 'Email atau password yang Anda masukkan salah.',
])->onlyInput('email');
}
/**
* Menampilkan Halaman Register
*/
public function showRegister()
{
$data_kolat = Kolat::all();
return view('login-register.register', compact('data_kolat'));
}
/**
* Proses Register (User & Anggota)
*/
public function register(Request $request)
{
// 1. Validasi Input
$request->validate([
'email' => 'required|email|unique:users,email',
'password' => 'required|min:6|confirmed',
'nama_lengkap' => 'required|string|max:255',
'no_hp' => 'required',
'jenis_kelamin' => 'required|in:L,P',
'tempat_lahir' => 'required',
'tgl_lahir' => 'required|date',
'alamat' => 'required',
'kolat_id' => 'required|exists:kolat,id',
'tingkatan' => 'required',
'tgl_gabung' => 'required|date',
]);
// Cari ID Role "Anggota"
$role = Role::where('nama_role', 'Anggota')->first();
$role_id = $role ? $role->id : 2;
// Gunakan Transaction agar data User & Anggota sinkron
DB::beginTransaction();
try {
// 2. Buat Akun User
$user = User::create([
'email' => $request->email,
'password' => Hash::make($request->password),
// 'role_id' => $role_id,
]);
// 3. Logika Penomoran Induk JB-xxx
$terakhir = Anggota::orderBy('id', 'desc')->first();
$nomor_urut = $terakhir ? sprintf("%03d", intval(substr($terakhir->no_induk, 3)) + 1) : "001";
$no_induk_baru = "JB-" . $nomor_urut;
// 4. Buat Profil Anggota
$anggota =Anggota::create([
'user_id' => $user->id,
'no_induk' => $no_induk_baru,
'nama_lengkap' => $request->nama_lengkap,
// 'role_id' => $role_id,
'jenis_kelamin' => $request->jenis_kelamin,
'tempat_lahir' => $request->tempat_lahir,
'tgl_lahir' => $request->tgl_lahir,
'no_hp' => $request->no_hp,
'kolat_id' => $request->kolat_id,
'tingkatan' => $request->tingkatan,
'tgl_gabung' => $request->tgl_gabung,
'alamat' => $request->alamat,
'catatan_medis' => $request->catatan_medis,
'jabatan' => 'anggota',
'status' => 'Non-Aktif', // Default sesuai permintaanmu
]);
$anggota->roles()->attach($role_id);
DB::commit();
return redirect()->route('login')->with('success', 'Registrasi Berhasil! Akun Anda akan segera diverifikasi oleh Admin.');
} catch (\Exception $e) {
DB::rollBack();
Log::error("Register Error: " . $e->getMessage());
return back()->withInput()->with('error', 'Terjadi kesalahan sistem saat mendaftar.');
}
}
/**
* Proses Logout
*/
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/login')->with('success', 'Anda telah berhasil keluar.');
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
}

View File

@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers;
use App\Models\Anggota;
use App\Models\Kolat;
use App\Models\Presensi;
use App\Models\Role;
use App\Models\Spp;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
public function index()
{
// --- 1. DATA STATISTIK UTAMA ---
// Menghitung anggota dengan status Aktif
// // Menghitung jumlah Kolat yang terdaftar
$total_kolat = Kolat::count();
// $roleAnggota = Role::where('nama_role', 'Anggota')->first();
// $rolePelatih = Role::where('nama_role', 'Pelatih')->first();
// 2. Hitung jumlah "Anggota" (Hanya yang ber-role 'Anggota' DAN berstatus 'Aktif')
$total_anggota_aktif = Anggota::where('status', 'Aktif')
->whereHas('roles', function($q) {
$q->where('nama_role', 'Anggota');
})
->count();
// 3. Hitung jumlah "Pelatih" (Hanya yang ber-role 'Pelatih' DAN berstatus 'Aktif')
$total_pelatih = Anggota::where('status', 'Aktif')
->whereHas('roles', function($q) {
$q->where('nama_role', 'Pelatih');
})
->count();
// Menghitung SPP yang belum lunas (Pending atau Belum Lunas)
// Pastikan 'pending' dan 'belum_lunas' sesuai dengan isi kolom status di database Anda
$spp_pending = Spp::whereIn('status', ['pending', 'belum_lunas'])->count();
// --- 2. DATA GRAFIK LINE (TREN PRESENSI 7 HARI) ---
// Mengambil data kehadiran 7 hari terakhir untuk grafik
$grafikPresensi = Presensi::select(
DB::raw('DATE(created_at) as date'),
DB::raw('count(*) as total')
)
->where('status', 'hadir')
->where('created_at', '>=', now()->subDays(7))
->groupBy('date')
->orderBy('date', 'ASC')
->get();
// --- 3. DATA GRAFIK DOUGHNUT (KOMPOSISI TINGKATAN) ---
// Menghitung persebaran anggota berdasarkan kolom 'tingkatan'
$persebaran_sabuk = Anggota::select('tingkatan', DB::raw('count(*) as total'))
->groupBy('tingkatan')
->get();
// --- 4. DATA AKTIVITAS TERBARU (DARI TRANSAKSI SPP) ---
// Mengambil 5 transaksi SPP terbaru beserta data anggotanya
$aktivitas = Spp::with('anggota')
->latest()
->take(5)
->get()
->map(function ($item) {
// Menentukan warna badge berdasarkan status
$statusLower = strtolower($item->status);
$badge = 'danger'; // Default
if ($statusLower == 'lunas') $badge = 'success';
if ($statusLower == 'pending') $badge = 'warning';
return [
'waktu' => $item->created_at->format('d/m/Y H:i'),
'kegiatan' => 'Pembayaran SPP: ' . ($item->keterangan ?? 'Iuran Bulanan'),
'user' => $item->anggota->nama_lengkap ?? 'Anggota', // Pastikan kolom 'nama_lengkap' ada
'status' => strtoupper($item->status),
'badge' => $badge
];
});
// --- 5. LOGIKA KELAYAKAN (RINGKASAN REKAP) ---
// Menarik data anggota yang memenuhi syarat kehadiran 75%
// Eager Loading 'presensi' agar query lebih cepat
$anggota = Anggota::with('presensi')->get();
$jumlah_layak_ujian = $anggota->filter(function ($item) {
$total = $item->presensi->count();
$hadir = $item->presensi->where('status', 'hadir')->count();
$persen = $total > 0 ? ($hadir / $total) * 100 : 0;
return $persen >= 75;
})->count();
// --- 6. RETURN KE VIEW ---
// Pastikan path view sesuai (folder dashboard, file dashboard.blade.php)
return view('dashboard.dashboard', compact(
'total_anggota_aktif',
'total_pelatih',
'total_kolat',
'spp_pending',
'grafikPresensi',
'persebaran_sabuk',
'aktivitas',
'jumlah_layak_ujian'
));
}
}

View File

@ -0,0 +1,150 @@
<?php
namespace App\Http\Controllers;
use App\Models\Jadwal;
use App\Models\Kolat;
use App\Models\Anggota;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Request;
class JadwalController extends Controller
{
// 1. Menampilkan Daftar Jadwal
public function index()
{
// Tambahkan 'presensi.anggota' agar modal konfirmasi bisa nampilin siapa aja yang hadir
$data_jadwal = Jadwal::with(['kolat', 'pelatih', 'presensi.anggota'])
->orderBy('tanggal', 'desc')
->orderBy('jam_mulai', 'desc')
->get();
return view('jadwal.jadwal', compact('data_jadwal'));
}
// 2. Proses Konfirmasi Latihan Selesai (Update Status + Upload Foto Kegiatan)
public function konfirmasiSelesai(Request $request, $id)
{
$jadwal = Jadwal::findOrFail($id);
// Validasi input dari modal
$request->validate([
'foto_bukti' => 'required|image|mimes:jpg,jpeg,png|max:2048',
'catatan_latihan' => 'nullable|string'
]);
// Proses upload foto kegiatan pelatih ke folder storage/app/public/kegiatan_pelatih
if ($request->hasFile('foto_bukti')) {
$path = $request->file('foto_bukti')->store('kegiatan_pelatih', 'public');
$jadwal->foto_bukti = $path;
}
// Simpan catatan dan ubah status menjadi selesai
$jadwal->catatan_latihan = $request->catatan_latihan;
$jadwal->status = 'selesai';
$jadwal->save();
return redirect()->route('jadwal.index')
->with('success', 'Latihan ' . $jadwal->judul_kegiatan . ' telah dikonfirmasi selesai!');
}
// 3. Menampilkan Form Tambah Jadwal
public function create()
{
$data_kolat = Kolat::all();
// Ambil Anggota yang memiliki role 'Pelatih' dan statusnya 'Aktif'
$data_pelatih = Anggota::whereHas('roles', function($query) {
$query->where('nama_role', 'Pelatih');
})
->with('kolatLatihan')
->where('status', 'Aktif')
->orderBy('nama_lengkap', 'asc')
->get();
return view('jadwal.jadwalcreate', compact('data_kolat', 'data_pelatih'));
}
// 4. Proses Simpan Jadwal Baru
public function store(Request $request)
{
$request->validate([
'judul_kegiatan' => 'required|string|max:255',
'kolat_id' => 'required|exists:kolat,id',
'pelatih_id' => 'required|exists:anggotas,id',
'jenis' => 'required|in:Rutin,Tambahan,Pengumuman',
'tanggal' => 'required|date',
'jam_mulai' => 'required',
'lokasi' => 'required|string',
]);
Jadwal::create($request->all());
return redirect()->route('jadwal.index')
->with('success', 'Jadwal latihan berhasil ditambahkan!');
}
// 5. Menampilkan Detail Jadwal (Opsional jika pakai modal)
public function show($id)
{
$jadwal = Jadwal::with(['kolat', 'pelatih'])->findOrFail($id);
return view('jadwal.show', compact('jadwal'));
}
// 6. Menampilkan Form Edit Jadwal
public function edit($id)
{
$jadwal = Jadwal::findOrFail($id);
$data_kolat = Kolat::all();
// Tetap filter agar hanya muncul daftar pelatih
$data_pelatih = Anggota::whereHas('roles', function($query) {
$query->where('nama_role', 'Pelatih');
})
->with('kolatLatihan')
->where('status', 'Aktif')
->orderBy('nama_lengkap', 'asc')
->get();
return view('jadwal.editjadwal', compact('jadwal', 'data_kolat', 'data_pelatih'));
}
// 7. Proses Update Jadwal
public function update(Request $request, $id)
{
$jadwal = Jadwal::findOrFail($id);
$request->validate([
'judul_kegiatan' => 'required|string|max:255',
'kolat_id' => 'required|exists:kolat,id',
'pelatih_id' => 'required|exists:anggotas,id',
'jenis' => 'required|in:Rutin,Tambahan,Pengumuman',
'tanggal' => 'required|date',
'jam_mulai' => 'required',
'lokasi' => 'required|string',
]);
$jadwal->update($request->all());
return redirect()->route('jadwal.index')
->with('success', 'Jadwal berhasil diperbarui!');
}
// 8. Proses Hapus Jadwal
public function destroy($id)
{
$jadwal = Jadwal::findOrFail($id);
// Cek dan hapus file foto bukti dari storage jika ada, biar hosting nggak penuh
if ($jadwal->foto_bukti) {
Storage::disk('public')->delete($jadwal->foto_bukti);
}
$jadwal->delete();
return redirect()->route('jadwal.index')
->with('success', 'Jadwal telah dihapus dari sistem.');
}
}

View File

@ -0,0 +1,161 @@
<?php
namespace App\Http\Controllers;
use App\Exports\KasExport;
use App\Models\Kas;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
class KasController extends Controller
{
public function index(Request $request)
{
// Menggunakan scope query untuk filter
$query = Kas::query();
if ($request->filled('jenis')) {
$query->where('jenis', $request->jenis);
}
if ($request->filled('dari') && $request->filled('sampai')) {
$query->whereBetween('tanggal', [$request->dari, $request->sampai]);
}
$data_kas = $query->orderBy('tanggal', 'asc')->orderBy('id', 'asc')->get();
// Statistik tetap menggunakan data keseluruhan
$saldo_sekarang = Kas::saldoTerakhir();
$pemasukan_bulan_ini = Kas::where('jenis', 'masuk')
->whereMonth('tanggal', now()->month)
->whereYear('tanggal', now()->year)
->sum('nominal');
$pengeluaran_bulan_ini = Kas::where('jenis', 'keluar')
->whereMonth('tanggal', now()->month)
->whereYear('tanggal', now()->year)
->sum('nominal');
return view('kas.index', compact('data_kas', 'saldo_sekarang', 'pemasukan_bulan_ini', 'pengeluaran_bulan_ini'));
}
/**
* Fungsi untuk menyimpan data transaksi baru
*/
public function store(Request $request)
{
// 1. Validasi input
$request->validate([
'tanggal' => 'required|date',
'jenis' => 'required|in:masuk,keluar',
'kategori' => 'required|string',
'nominal' => 'required|numeric|min:0',
]);
// 2. Ambil saldo terakhir untuk kalkulasi saldo_akhir
$saldo_terakhir = Kas::saldoTerakhir();
// 3. Hitung saldo baru
if ($request->jenis == 'masuk') {
$saldo_baru = $saldo_terakhir + $request->nominal;
} else {
$saldo_baru = $saldo_terakhir - $request->nominal;
}
// 4. Simpan ke database
Kas::create([
'tanggal' => $request->tanggal,
'jenis' => $request->jenis,
'kategori' => $request->kategori,
'nominal' => $request->nominal,
'keterangan' => $request->keterangan,
'saldo_akhir' => $saldo_baru,
]);
return redirect()->route('kas.index')->with('success', 'Transaksi berhasil ditambahkan!');
}
public function destroy($id)
{
$kas = Kas::findOrFail($id);
$kas->delete();
return redirect()->back()->with('success', 'Transaksi berhasil dihapus!');
}
public function update(Request $request, $id)
{
$request->validate([
'tanggal' => 'required|date',
'jenis' => 'required|in:masuk,keluar',
'kategori' => 'required|string',
'nominal' => 'required|numeric',
]);
$kas = Kas::findOrFail($id);
// Logika perhitungan ulang saldo:
// Kita ambil saldo sebelum transaksi ini terjadi
$saldo_sebelumnya = Kas::where('id', '<', $kas->id)->orderBy('id', 'desc')->first()->saldo_akhir ?? 0;
// Hitung saldo akhir baru untuk baris ini
$saldo_akhir_baru = ($request->jenis == 'masuk')
? $saldo_sebelumnya + $request->nominal
: $saldo_sebelumnya - $request->nominal;
$kas->update([
'tanggal' => $request->tanggal,
'jenis' => $request->jenis,
'kategori' => $request->kategori,
'nominal' => $request->nominal,
'keterangan' => $request->keterangan,
'saldo_akhir' => $saldo_akhir_baru,
]);
// Catatan: Idealnya setelah edit, semua baris setelahnya juga diupdate saldonya.
// Tapi untuk tahap awal, ini sudah cukup fungsional.
return redirect()->back()->with('success', 'Transaksi berhasil diperbarui!');
}
public function exportExcel(Request $request)
{
$query = Kas::query();
// Terapkan filter yang sama dengan index
if ($request->filled('jenis')) {
$query->where('jenis', $request->jenis);
}
if ($request->filled('dari') && $request->filled('sampai')) {
$query->whereBetween('tanggal', [$request->dari, $request->sampai]);
}
$data = $query->orderBy('tanggal', 'asc')->get();
return Excel::download(new KasExport($data), 'Laporan_Kas_TapakMP_'.date('Ymd').'.xlsx');
}
public function exportPdf(Request $request)
{
$query = Kas::query();
if ($request->filled('jenis')) {
$query->where('jenis', $request->jenis);
}
if ($request->filled('dari') && $request->filled('sampai')) {
$query->whereBetween('tanggal', [$request->dari, $request->sampai]);
}
$data_kas = $query->orderBy('tanggal', 'asc')->get();
// Hitung total untuk ringkasan di bawah tabel PDF
$total_masuk = $data_kas->where('jenis', 'masuk')->sum('nominal');
$total_keluar = $data_kas->where('jenis', 'keluar')->sum('nominal');
$pdf = Pdf::loadView('kas.export_pdf', compact('data_kas', 'total_masuk', 'total_keluar'));
return $pdf->download('Laporan_Kas_TapakMP_'.date('Ymd').'.pdf');
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace App\Http\Controllers;
use App\Models\Kolat;
use Illuminate\Http\Request;
class KolatController extends Controller
{
// 1. Tampilkan Halaman Utama Kolat
public function kolat()
{
$data_kolat = Kolat::all();
return view('kolat.kolat', compact('data_kolat'));
}
// membuat data dan Simpan Data Kolat Baru
public function create()
{
return view('kolat.kolatcreate');
}
public function store(Request $request)
{
$request->validate([
'nama_kolat' => 'required|unique:kolat,nama_kolat',
'alamat_kolat' => 'nullable'
]);
Kolat::create($request->all());
return redirect()->route('kolat.index')->with('success', 'Kolat baru berhasil ditambahkan!');
}
public function edit($id)
{
$kolat = Kolat::findOrFail($id);
return view('kolat.editkolat', compact('kolat'));
}
public function update(Request $request, $id)
{
$request->validate([
'nama_kolat' => 'required|unique:kolat,nama_kolat,'.$id,
'alamat_kolat' => 'nullable'
]);
$kolat = Kolat::findOrFail($id);
$kolat->update($request->all());
return redirect()->route('kolat.index')->with('success', 'Data Kolat berhasil diperbarui!');
}
// 4. Hapus Data Kolat
// public function destroy($id)
// {
// $kolat = Kolat::findOrFail($id);
// $kolat->delete();
// return redirect()->back()->with('success', 'Kolat telah dihapus!');
// }
public function destroy($id)
{
$kolat = Kolat::findOrFail($id);
// Cek apakah ada anggota di kolat ini
if ($kolat->anggota()->count() > 0) {
return redirect()->back()->with('error', 'Kolat tidak bisa dihapus karena masih ada anggota yang terdaftar di dalamnya!');
}
$kolat->delete();
return redirect()->back()->with('success', 'Kolat telah dihapus!');
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class LandingPageController extends Controller
{
//
}

View File

@ -0,0 +1,132 @@
<?php
namespace App\Http\Controllers;
use App\Exports\PresensiExport;
use App\Models\Anggota;
use App\Models\Kas;
use App\Models\Kolat;
use App\Models\Presensi;
use App\Models\Spp;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
class LaporanController extends Controller
{
// Halaman Laporan Anggota
public function anggota(Request $request)
{
$data_kolat = Kolat::all();
$query = Anggota::query();
if ($request->filled('kolat_id')) {
$query->where('kolat_id', $request->kolat_id);
}
if ($request->filled('status')) {
$query->where('status', $request->status);
}
$data_anggota = $query->with('kolat')->get();
return view('laporan.anggota', compact('data_anggota', 'data_kolat'));
}
// Halaman Laporan Keuangan (Kas)
public function kas(Request $request)
{
$query = Kas::query();
if ($request->filled('dari') && $request->filled('sampai')) {
$query->whereBetween('tanggal', [$request->dari, $request->sampai]);
}
$data_kas = $query->orderBy('tanggal', 'asc')->get();
return view('laporan.kas', compact('data_kas'));
}
// Halaman Laporan SPP
public function spp(Request $request)
{
// 1. Ambil semua Kolat untuk isi Dropdown Filter
$data_kolat = Kolat::all();
$query = Spp::with(['anggota', 'kolat']);
// 2. Logika Filter Kolat
if ($request->filled('kolat_id')) {
$query->where('kolat_id', $request->kolat_id);
}
// 3. Filter lainnya
if ($request->filled('bulan')) $query->where('bulan', $request->bulan);
if ($request->filled('tahun')) $query->where('tahun', $request->tahun);
if ($request->filled('status')) $query->where('status', $request->status);
$data_spp = $query->get();
return view('laporan.spp', compact('data_spp', 'data_kolat'));
}
public function presensi(Request $request)
{
$data_kolat = \App\Models\Kolat::all();
$query = Presensi::with(['anggota', 'jadwal.kolat', 'jadwal.pelatih', 'pelatihVerifikator']);
// Filter Kolat
if ($request->filled('kolat_id')) {
$query->whereHas('jadwal', function($q) use ($request) {
$q->where('kolat_id', $request->kolat_id);
});
}
// Filter Tanggal
if ($request->filled('dari') && $request->filled('sampai')) {
$query->whereHas('jadwal', function($q) use ($request) {
$q->whereBetween('tanggal', [$request->dari, $request->sampai]);
});
}
// Filter Status
if ($request->filled('status')) {
$query->where('status', $request->status);
}
$data_presensi = $query->orderBy('created_at', 'desc')->get();
return view('laporan.presensi', compact('data_presensi', 'data_kolat'));
}
public function presensiExcel(Request $request)
{
// Logika query yang sama dengan di atas
$query = Presensi::with(['anggota', 'jadwal.kolat', 'jadwal.pelatih', 'pelatihVerifikator']);
if ($request->filled('kolat_id')) {
$query->whereHas('jadwal', function($q) use ($request) { $q->where('kolat_id', $request->kolat_id); });
}
if ($request->filled('dari') && $request->filled('sampai')) {
$query->whereHas('jadwal', function($q) use ($request) { $q->whereBetween('tanggal', [$request->dari, $request->sampai]); });
}
$data = $query->get();
return Excel::download(new PresensiExport($data), 'Laporan_Presensi_TapakMP_' . date('Ymd') . '.xlsx');
}
public function presensiPdf(Request $request)
{
$query = Presensi::with(['anggota', 'jadwal.kolat', 'jadwal.pelatih', 'pelatihVerifikator']);
if ($request->filled('kolat_id')) {
$query->whereHas('jadwal', function($q) use ($request) { $q->where('kolat_id', $request->kolat_id); });
}
if ($request->filled('dari') && $request->filled('sampai')) {
$query->whereHas('jadwal', function($q) use ($request) { $q->whereBetween('tanggal', [$request->dari, $request->sampai]); });
}
$data_presensi = $query->get();
$pdf = Pdf::loadView('laporan.presensi_pdf', compact('data_presensi'));
$pdf->setPaper('a4', 'landscape');
return $pdf->download('Laporan_Presensi_TapakMP_' . date('Ymd') . '.pdf');
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers;
use App\Models\Anggota;
use App\Models\Jadwal;
use App\Models\Kas;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class MemberDashboardController extends Controller
{
// public function index()
// {
// // 1. Ambil data profil anggota yang sedang login
// $user = Auth::user();
// $anggota = Anggota::where('user_id', $user->id)->first();
// if (!$anggota) {
// return redirect('/')->with('error', 'Data anggota tidak ditemukan.');
// }
// // 2. Ambil Jadwal Latihan (Hanya yang sesuai dengan Kolat si anggota)
// $jadwal = Jadwal::where('kolat_id', $anggota->kolat_id)
// ->where('tanggal', '>=', now())
// ->orderBy('tanggal', 'asc')
// ->take(5)
// ->get();
// // 3. Ambil Tagihan SPP (Contoh: mencari data di tabel kas yang belum lunas)
// // Asumsi Anda punya kolom 'anggota_id' dan 'status' di tabel Kas/SPP
// $tagihan_spp = Kas::where('no_induk', $anggota->no_induk)
// ->where('status', 'Pending')
// ->get();
// // 4. Hitung Ringkasan (Contoh: Total kehadiran)
// // Ini hanya dummy, silakan sesuaikan dengan tabel absensi Anda
// $persentase_hadir = 85;
// return view('anggota.dashboard', compact('anggota', 'jadwal', 'tagihan_spp', 'persentase_hadir'));
// }
public function index()
{
// 1. Ambil data profil anggota yang sedang login
$user = Auth::user();
$anggota = Anggota::where('user_id', $user->id)->first();
if (!$anggota) {
return redirect('/')->with('error', 'Data anggota tidak ditemukan.');
}
// 2. Ambil Jadwal Latihan
// PERBAIKAN: Gunakan today() atau now()->toDateString() alih-alih now().
// now() membawa jam (misal 19:42), sehingga jadwal hari ini yang jamnya sudah lewat bisa tidak terbaca.
$jadwal = Jadwal::where('kolat_id', $anggota->kolat_id)
->where('tanggal', '>=', today())
->orderBy('tanggal', 'asc')
->take(5)
->get();
// 3. Ambil Tagihan SPP
// PERBAIKAN PENTING: Tadi kita sepakat status awalnya adalah 'belum_bayar', bukan 'Pending'
$tagihan_spp = $anggota->spp()
->where('status', 'belum_bayar')
->get();
// 4. Hitung Ringkasan Kehadiran
$total_latihan = $anggota->riwayatPresensi()->count();
$total_hadir = $anggota->riwayatPresensi()->where('status', 'Hadir')->count();
$persentase_hadir = 0;
if ($total_latihan > 0) {
$persentase_hadir = round(($total_hadir / $total_latihan) * 100);
}
return view('anggota.dashboard', compact('anggota', 'jadwal', 'tagihan_spp', 'persentase_hadir'));
}
}

View File

@ -0,0 +1,134 @@
<?php
namespace App\Http\Controllers;
use App\Models\Anggota;
use App\Models\Kolat;
use App\Models\Role;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class PelatihController extends Controller
{
// Menampilkan daftar anggota yang sudah menjadi pelatih
public function index()
{
// Asumsi: Role pelatih memiliki nama 'Pelatih' di tabel roles
$data_pelatih = Anggota::whereHas('roles', function($q) {
$q->where('nama_role', 'Pelatih');
})->with('kolatLatihan')->get();
// })->with('kolat')->get();
return view('pelatih.pelatih', compact('data_pelatih'));
}
// Menampilkan form untuk upgrade anggota ke pelatih
public function create()
{
// Ambil anggota yang role-nya masih 'Anggota' (bukan pelatih)
$calon_pelatih = Anggota::whereHas('roles', function($q) {
$q->where('nama_role', 'Anggota');
})->whereDoesntHave('roles', function($q) {
$q->where('nama_role', 'Pelatih');
})->get();
$data_kolat = Kolat::all();
return view('pelatih.pelatihcreate', compact('calon_pelatih', 'data_kolat'));
}
// Proses Upgrade Anggota ke Pelatih
public function store(Request $request)
{
$request->validate([
'anggota_id' => 'required|exists:anggotas,id',
'no_sk' => 'required|unique:anggotas,no_sk',
'tgl_sk' => 'required|date',
'masa_berlaku' => 'required|date',
'foto_sk' => 'nullable|image|mimes:jpg,png,jpeg|max:2048',
'kolat_ids' => 'required|array',
'kolat_ids.*' => 'exists:kolat,id',
]);
// Cari data anggota berdasarkan ID yang dipilih
$anggota = Anggota::findOrFail($request->anggota_id);
// Cari ID Role Pelatih
$rolePelatih = Role::where('nama_role', 'Pelatih')->first();
// Data yang akan diupdate
$updateData = [
// 'role_id' => $rolePelatih->id,
// 'jabatan' => 'pelatih', // Update status jabatan
'no_sk' => $request->no_sk,
'tgl_sk' => $request->tgl_sk,
'masa_berlaku' => $request->masa_berlaku,
'status' => 'Aktif',
];
$jabatan_lama = strtolower($anggota->jabatan);
if (empty($jabatan_lama) || $jabatan_lama == 'anggota') {
$updateData['jabatan'] = 'Pelatih'; // Jika kosong/anggota biasa, set murni Pelatih
} else if (!str_contains($jabatan_lama, 'pelatih')) {
$updateData['jabatan'] = $anggota->jabatan . ', Pelatih'; // Gabungkan, misal: "Sekretaris, Pelatih"
}
// Handle upload foto SK jika ada
if ($request->hasFile('foto_sk')) {
$path = $request->file('foto_sk')->store('sk_pelatih', 'public');
$updateData['foto_sk'] = $path;
}
// $anggota->roles()->syncWithoutDetaching([$rolePelatih->id]);
// KODE BARU (Benar karena akan mengganti role Anggota menjadi murni Pelatih)
$anggota->roles()->sync([$rolePelatih->id]);
$anggota->kolatLatihan()->sync($request->kolat_ids);
// Eksekusi Update
$anggota->update($updateData);
return redirect()->route('pelatih.index')
->with('success', $anggota->nama_lengkap . ' berhasil diupgrade menjadi Pelatih!');
}
// Menampilkan form edit data kepelatihan (misal perpanjang SK)
public function edit($id)
{
$pelatih = Anggota::findOrFail($id);
$data_kolat = Kolat::all();
return view('pelatih.editpelatih', compact('pelatih', 'data_kolat'));
}
public function update(Request $request, $id)
{
$pelatih = Anggota::findOrFail($id);
$request->validate([
'nama_lengkap' => 'required|string|max:255',
'email' => 'required|email|unique:users,email,' . $pelatih->user_id, // validasi email user
'no_hp' => 'required',
'no_sk' => 'required|unique:anggotas,no_sk,' . $id, // pastikan nama tabel benar 'anggota'
'tgl_sk' => 'nullable|date', // tambahkan jika di form ada tgl_sk
'foto_sk' => 'nullable|image|max:2048',
'kolat_ids' => 'required|array',
'kolat_ids.*' => 'exists:kolat,id',
]);
// $data = $request->all();
$data = $request->except(['kolat_ids', '_token', '_method']);
if ($request->hasFile('foto_sk')) {
if ($pelatih->foto_sk) {
Storage::disk('public')->delete($pelatih->foto_sk);
}
$data['foto_sk'] = $request->file('foto_sk')->store('sk_pelatih', 'public');
}
$pelatih->update($data);
$pelatih->kolatLatihan()->sync($request->kolat_ids);
return redirect()->route('pelatih.index')->with('success', 'Data kepelatihan diperbarui!');
}
}

View File

@ -0,0 +1,112 @@
<?php
namespace App\Http\Controllers;
use App\Models\Anggota;
use App\Models\Role;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class PengurusController extends Controller
{
// Menampilkan Daftar Pengurus Cabang
public function index()
{
$data_pengurus = Anggota::whereHas('roles', function($q) {
$q->where('nama_role', 'Pengurus');
})->latest()->get();
return view('pengurus.index', compact('data_pengurus'));
}
// Menampilkan Form Angkat Pengurus Baru
public function create()
{
// Aturan: Harus sudah memiliki role Pelatih, tetapi BELUM memiliki role Pengurus
$calon_pengurus = Anggota::whereHas('roles', function($q) {
$q->where('nama_role', 'Pelatih');
})->whereDoesntHave('roles', function($q) {
$q->where('nama_role', 'Pengurus');
})->get();
return view('pengurus.create', compact('calon_pengurus'));
}
// Menyimpan Data Pengurus Baru ke Database
public function store(Request $request)
{
$request->validate([
'anggota_id' => 'required|exists:anggotas,id',
'jabatan_struktural' => 'required|string|max:255', // Contoh: Ketua, Sekretaris, Bendahara
]);
DB::beginTransaction();
try {
$anggota = Anggota::findOrFail($request->anggota_id);
$rolePengurus = Role::where('nama_role', 'Pengurus')->firstOrFail();
// Gabungkan jabatan lama (pelatih) dengan jabatan struktural baru
$jabatan_baru = $anggota->jabatan . ', Pengurus (' . $request->jabatan_struktural . ')';
// 1. Tambahkan role Pengurus ke tabel pivot tanpa menghapus role lama
$anggota->roles()->syncWithoutDetaching([$rolePengurus->id]);
// 2. Perbarui kolom jabatan strukturalnya
$anggota->update([
'jabatan' => $jabatan_baru
]);
DB::commit();
return redirect()->route('pengurus.index')
->with('success', $anggota->nama_lengkap . ' berhasil diangkat menjadi Pengurus!');
} catch (\Exception $e) {
DB::rollBack();
Log::error("Gagal mengangkat pengurus: " . $e->getMessage());
return back()->withInput()->with('error', 'Terjadi kesalahan sistem saat memproses data.');
}
}
// Menampilkan Form Edit Jabatan Struktural Pengurus
public function edit($id)
{
$pengurus = Anggota::whereHas('roles', function($q) {
$q->where('nama_role', 'Pengurus');
})->findOrFail($id);
// Ekstrak nilai di dalam kurung dari string jabatan untuk mempermudah edit di form
// Contoh: "Pelatih, Pengurus (Sekretaris)" diambil kata "Sekretaris"
preg_match('#\((.*?)\)#', $pengurus->jabatan, $match);
$jabatan_sekarang = $match[1] ?? '';
return view('pengurus.edit', compact('pengurus', 'jabatan_sekarang'));
}
// Membarui Jabatan Struktural Pengurus
public function update(Request $request, $id)
{
$request->validate([
'jabatan_struktural' => 'required|string|max:255',
]);
try {
$pengurus = Anggota::findOrFail($id);
// Set ulang string jabatannya dengan nilai struktural yang baru
$jabatan_baru = 'Pelatih, Pengurus (' . $request->jabatan_struktural . ')';
$pengurus->update([
'jabatan' => $jabatan_baru
]);
return redirect()->route('pengurus.index')
->with('success', 'Jabatan struktural ' . $pengurus->nama_lengkap . ' berhasil diperbarui!');
} catch (\Exception $e) {
Log::error("Gagal update jabatan pengurus: " . $e->getMessage());
return back()->with('error', 'Terjadi kesalahan sistem saat memperbarui data.');
}
}
}

View File

@ -0,0 +1,162 @@
<?php
namespace App\Http\Controllers;
use App\Models\Anggota;
use App\Models\Penilaian;
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class PenilaianController extends Controller
{
public function index()
{
// Mencari anggota berdasarkan relasi 'role' yang bernama 'Pelatih'
$data_pelatih = Anggota::whereHas('roles', function ($query) {
$query->where('nama_role', 'Pelatih');
})
->with('kolat')
->get();
return view('penilaian.index', compact('data_pelatih'));
}
public function create($id)
{
// 1. Tentukan ID Anggota Penilai (Lebih aman pakai operator ?? untuk mencegah error jika relasi anggota kosong)
$anggota_penilai_id = auth()->check() ? (auth()->user()->anggota->id ?? 1) : 1;
// 2. Tentukan rentang waktu bulan berjalan
$awalBulanIni = Carbon::now()->startOfMonth();
$akhirBulanIni = Carbon::now()->endOfMonth();
// 3. PENJAGA AWAL: Cek sebelum halaman form kuis terbuka
$sudahMenilai = Penilaian::where('anggota_id', $anggota_penilai_id)
->where('pelatih_id', $id)
->whereBetween('bulan_evaluasi', [$awalBulanIni, $akhirBulanIni])
->exists();
if ($sudahMenilai) {
// Jika sudah pernah menilai bulan ini, langsung tendang balik ke halaman daftar pelatih
return redirect()->route('penilaian.anggota_index')
->with('error', 'Anda sudah memberikan penilaian untuk pelatih ini pada bulan ini!');
}
$pelatih = Anggota::with('kolat')->findOrFail($id);
return view('penilaian.create', compact('pelatih'));
}
public function store(Request $request)
{
$request->validate([
'pelatih_id' => 'required',
'metode_pelatihan' => 'required|integer|min:1|max:5',
'komunikasi' => 'required|integer|min:1|max:5',
'sikap_kepribadian' => 'required|integer|min:1|max:5',
'kepemimpinan' => 'required|integer|min:1|max:5',
'konsistensi_komitmen' => 'required|integer|min:1|max:5',
'kedekatan_interpersonal' => 'required|integer|min:1|max:5',
'kritik_saran' => 'nullable|string'
]);
$anggota_penilai_id = auth()->check() ? (auth()->user()->anggota->id ?? 1) : 1;
$awalBulanIni = Carbon::now()->startOfMonth();
$akhirBulanIni = Carbon::now()->endOfMonth();
// 4. PENJAGA KEDUA: Memastikan keamanan data saat disubmit
$sudahMenilai = Penilaian::where('anggota_id', $anggota_penilai_id)
->where('pelatih_id', $request->pelatih_id)
->whereBetween('bulan_evaluasi', [$awalBulanIni, $akhirBulanIni])
->exists();
if ($sudahMenilai) {
return redirect()->route('penilaian.anggota_index')
->with('error', 'Gagal! Anda sudah menilai pelatih ini bulan ini.');
}
Penilaian::create([
'pelatih_id' => $request->pelatih_id,
'anggota_id' => $anggota_penilai_id,
'bulan_evaluasi' => $awalBulanIni,
'metode_pelatihan' => $request->metode_pelatihan,
'komunikasi' => $request->komunikasi,
'sikap_kepribadian' => $request->sikap_kepribadian,
'kepemimpinan' => $request->kepemimpinan,
'konsistensi_komitmen' => $request->konsistensi_komitmen,
'kedekatan_interpersonal' => $request->kedekatan_interpersonal,
'kritik_saran' => $request->kritik_saran,
]);
return redirect()->route('penilaian.anggota_index')->with('success', 'Penilaian pelatih berhasil disimpan!');
}
// FUNGSI INI KEMBALI DITAMBAHKAN KARENA SEMPAT TERHAPUS
public function show($id)
{
$pelatih = Anggota::with('kolat')->findOrFail($id);
// Hitung rata-rata tiap kriteria bulan ini
$rekap = Penilaian::where('pelatih_id', $id)
->whereMonth('bulan_evaluasi', now()->month)
->selectRaw('
AVG(metode_pelatihan) as avg_metode,
AVG(komunikasi) as avg_komunikasi,
AVG(sikap_kepribadian) as avg_sikap,
AVG(kepemimpinan) as avg_kepemimpinan,
AVG(konsistensi_komitmen) as avg_komitmen,
AVG(kedekatan_interpersonal) as avg_interpersonal
')->first();
// Ambil ulasan/kritik saran (Pluck agar anonim)
$ulasan = Penilaian::where('pelatih_id', $id)
->whereMonth('bulan_evaluasi', now()->month)
->whereNotNull('kritik_saran')
->pluck('kritik_saran');
return view('penilaian.rekap', compact('pelatih', 'rekap', 'ulasan'));
}
public function exportPdf($id)
{
// 1. Ambil data pelatih
$pelatih = Anggota::with('kolat')->findOrFail($id);
// 2. Hitung rata-rata nilai (Rekap)
$rekap = Penilaian::where('pelatih_id', $id)
->whereMonth('bulan_evaluasi', now()->month)
->selectRaw('
AVG(metode_pelatihan) as avg_metode,
AVG(komunikasi) as avg_komunikasi,
AVG(sikap_kepribadian) as avg_sikap,
AVG(kepemimpinan) as avg_kepemimpinan,
AVG(konsistensi_komitmen) as avg_komitmen,
AVG(kedekatan_interpersonal) as avg_interpersonal
')->first();
// 3. Ambil ulasan ulasan anggota
$ulasan = Penilaian::where('pelatih_id', $id)
->whereMonth('bulan_evaluasi', now()->month)
->whereNotNull('kritik_saran')
->pluck('kritik_saran');
// 4. Panggil file view "laporan_pdf" sesuai nama filemu
$pdf = Pdf::loadView('penilaian.laporan_pdf', compact('pelatih', 'rekap', 'ulasan'));
// 5. Download file PDF
return $pdf->download('Laporan-Performa-' . $pelatih->nama_lengkap . '.pdf');
}
public function indexAnggota()
{
// Ubah juga bagian ini menggunakan whereHas
$data_pelatih = Anggota::whereHas('roles', function ($query) {
$query->where('nama_role', 'Pelatih');
})
->with('kolat')
->get();
return view('penilaian.anggota_index', compact('data_pelatih'));
}
}

View File

@ -0,0 +1,265 @@
<?php
namespace App\Http\Controllers;
use App\Models\Anggota;
// TAMBAHAN WAJIB: Memanggil Facades dan Model yang digunakan
use App\Models\Jadwal;
use App\Models\Presensi;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class PresensiController extends Controller
{
/**
* AKTOR: PENGURUS/PELATIH
* Menampilkan daftar semua jadwal untuk dipilih mana yang mau dicek presensinya
*/
public function index()
{
// Menggunakan Eloquent agar lebih konsisten dengan fungsi lainnya
$data_jadwal = Jadwal::with(['kolat', 'pelatih'])
->orderBy('tanggal', 'desc')
->orderBy('jam_mulai', 'desc')
->get();
return view('presensi.pengurus.index', compact('data_jadwal'));
}
/**
* AKTOR: PENGURUS/PELATIH
* Menampilkan detail siapa saja yang sudah scan/absen di jadwal tertentu
*/
public function kehadiran($jadwal_id)
{
$jadwal = Jadwal::with(['kolat', 'pelatih'])->findOrFail($jadwal_id);
$data_presensi = Presensi::with('anggota', 'pelatihVerifikator')
->where('jadwal_id', $jadwal_id)
->get();
// 1. Ambil array ID anggota yang SUDAH absen
$id_sudah_absen = $data_presensi->pluck('anggota_id')->toArray();
// 2. Ambil anggota yang se-Kolat, status Aktif, tapi ID-nya BELUM ADA di daftar absen
$anggota_belum_absen = Anggota::where('kolat_id', $jadwal->kolat_id)
->where('status', 'Aktif')
->whereNotIn('id', $id_sudah_absen)
->get();
// Kirim variabel baru ini ke View
return view('presensi.pengurus.kehadiran', compact('jadwal', 'data_presensi', 'anggota_belum_absen'));
}
/**
* AKTOR: PENGURUS/PELATIH
* Mensahkan (Verify) absensi anggota yang masuk lewat scan QR
*/
public function konfirmasi(Request $request, $presensi_id)
{
$presensi = Presensi::findOrFail($presensi_id);
$request->validate([
'action' => 'required|in:sah,tolak',
'keterangan' => 'nullable|string' // <-- Tambahan validasi keterangan
]);
if ($request->action == 'sah') {
$presensi->update([
'is_verified' => true,
'status' => 'Hadir',
'verified_by' => Auth::user()->anggota->id,
]);
$pesan = 'Kehadiran ' . $presensi->anggota->nama_lengkap . ' berhasil disahkan!';
} else {
$presensi->update([
'is_verified' => true,
'status' => 'Alfa',
'verified_by' => Auth::user()->anggota->id,
'keterangan' => $request->keterangan // <-- Simpan alasan penolakan
]);
$pesan = 'Absensi ' . $presensi->anggota->nama_lengkap . ' ditolak (Alfa).';
}
return redirect()->back()->with('success', $pesan);
}
/**
* AKTOR: PENGURUS/PELATIH
* Input manual jika anggota lupa bawa HP (Otomatis is_verified = true)
*/
public function storeManual(Request $request)
{
$request->validate([
'jadwal_id' => 'required|exists:jadwals,id',
'anggota_id' => 'required|exists:anggotas,id',
'status' => 'required|in:Hadir,Izin,Sakit,Alfa',
'keterangan' => 'nullable|string' // <-- Tambahan validasi keterangan
]);
$sudah_absen = Presensi::where('jadwal_id', $request->jadwal_id)
->where('anggota_id', $request->anggota_id)
->first();
if ($sudah_absen) {
return redirect()->back()->with('error', 'Anggota tersebut sudah memiliki catatan kehadiran di jadwal ini!');
}
Presensi::create([
'jadwal_id' => $request->jadwal_id,
'anggota_id' => $request->anggota_id,
'status' => $request->status,
'keterangan' => $request->keterangan, // <-- Simpan keterangan manual
'waktu_presensi' => now(),
'is_verified' => true,
'verified_by' => Auth::user()->anggota->id,
]);
return redirect()->back()->with('success', 'Presensi manual berhasil disimpan!');
}
/**
* AKTOR: ANGGOTA
* Melihat daftar jadwal latihan hari ini untuk di-scan
*/
/**
* AKTOR: ANGGOTA
* Melihat daftar jadwal latihan hari ini dan riwayat 7 hari terakhir
*/
public function indexAnggota()
{
if (!Auth::check()) {
return redirect()->route('login')->with('error', 'Silakan login terlebih dahulu.');
}
$user = Auth::user();
$now = \Carbon\Carbon::now('Asia/Jakarta');
$hari_ini = $now->format('Y-m-d');
// Ambil tanggal 7 hari yang lalu
$seminggu_lalu = $now->copy()->subDays(7)->format('Y-m-d');
if (!$user->anggota) {
return redirect()->route('presensi.index')->with('error', 'Akun Anda tidak terdaftar sebagai peserta latihan.');
}
// Ambil jadwal dalam rentang 7 hari terakhir hingga hari ini
$jadwal_terbaru = Jadwal::with('pelatih')
->whereBetween('tanggal', [$seminggu_lalu, $hari_ini])
->where('kolat_id', $user->anggota->kolat_id)
->orderBy('tanggal', 'desc')
->orderBy('jam_mulai', 'desc')
->get();
$presensi_saya = Presensi::where('anggota_id', $user->anggota->id)
->whereIn('jadwal_id', $jadwal_terbaru->pluck('id'))
->get()
->keyBy('jadwal_id');
return view('presensi.anggota.index', compact('jadwal_terbaru', 'presensi_saya', 'now', 'hari_ini'));
}
/**
* AKTOR: ANGGOTA
* Menampilkan halaman kamera scanner
*/
public function scan($jadwal_id)
{
$jadwal = Jadwal::findOrFail($jadwal_id);
return view('presensi.anggota.scan', compact('jadwal'));
}
/**
* AKTOR: ANGGOTA
* Menyimpan data presensi hasil scan (Menunggu Verifikasi Pelatih)
*/
public function storeMandiri(Request $request)
{
$request->validate([
'jadwal_id' => 'required|exists:jadwals,id',
]);
$user = Auth::user();
$profilAnggota = $user->anggota;
if (!$profilAnggota) {
return redirect()->back()->with('error', 'Data profil tidak lengkap.');
}
$jadwal = Jadwal::findOrFail($request->jadwal_id);
$now = \Carbon\Carbon::now('Asia/Jakarta');
// Validasi Waktu: Gabungkan tanggal dan jam untuk pengecekan presisi
$jam_selesai = $jadwal->jam_selesai
? $jadwal->jam_selesai
: \Carbon\Carbon::parse($jadwal->jam_mulai)->addHours(3)->format('H:i:s');
$batas_waktu = \Carbon\Carbon::parse($jadwal->tanggal . ' ' . $jam_selesai, 'Asia/Jakarta');
if ($now->gt($batas_waktu)) {
return redirect()->route('presensi.anggota.index')->with('error', 'Sesi absensi sudah berakhir pada pukul ' . substr($jam_selesai, 0, 5) . ' WIB.');
}
if ($jadwal->status == 'selesai') {
return redirect()->route('presensi.anggota.index')->with('error', 'Sesi latihan sudah ditutup oleh pelatih.');
}
// Cek apakah sudah absen
$sudah_absen = Presensi::where('jadwal_id', $request->jadwal_id)
->where('anggota_id', $profilAnggota->id)
->exists();
if ($sudah_absen) {
return redirect()->route('presensi.anggota.index')->with('error', 'Kamu sudah melakukan absensi untuk jadwal ini!');
}
Presensi::create([
'jadwal_id' => $request->jadwal_id,
'anggota_id' => $profilAnggota->id,
'status' => 'Hadir',
'waktu_presensi' => $now,
'is_verified' => false,
'verified_by' => null,
]);
return redirect()->route('presensi.anggota.index')->with('success', 'Berhasil scan! Silakan minta Pelatih untuk konfirmasi kehadiran.');
}
/**
* AKTOR: ANGGOTA
* Menampilkan riwayat lengkap presensi dengan filter bulan dan tahun
*/
public function riwayatAnggota(Request $request)
{
if (!Auth::check()) {
return redirect()->route('login')->with('error', 'Silakan login terlebih dahulu.');
}
$user = Auth::user();
if (!$user->anggota) {
return redirect()->route('presensi.index')->with('error', 'Profil anggota tidak ditemukan.');
}
// Tangkap request filter bulan & tahun (Default: Bulan & Tahun saat ini)
$bulan = $request->input('bulan', date('m'));
$tahun = $request->input('tahun', date('Y'));
// Ambil semua jadwal sesuai filter bulan & tahun di Kolat anggota tersebut
$jadwal_riwayat = Jadwal::with('pelatih')
->where('kolat_id', $user->anggota->kolat_id)
->whereMonth('tanggal', $bulan)
->whereYear('tanggal', $tahun)
->orderBy('tanggal', 'desc')
->orderBy('jam_mulai', 'desc')
->get();
// Ambil data presensi anggota pada jadwal-jadwal tersebut
$presensi_saya = Presensi::where('anggota_id', $user->anggota->id)
->whereIn('jadwal_id', $jadwal_riwayat->pluck('id'))
->get()
->keyBy('jadwal_id');
return view('presensi.anggota.riwayat', compact('jadwal_riwayat', 'presensi_saya', 'bulan', 'tahun'));
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
class ProfileController extends Controller
{
/**
* Menampilkan form profil anggota yang sedang login.
*/
public function edit()
{
// Mengambil data anggota yang berelasi dengan User yang sedang login
$anggota = Auth::user()->anggota;
if (!$anggota) {
return redirect()->back()->with('error', 'Data profil tidak ditemukan.');
}
return view('profil.edit', compact('anggota'));
}
/**
* Menyimpan perubahan data yang diperbaiki anggota.
*/
public function update(Request $request)
{
$anggota = Auth::user()->anggota;
// 1. Validasi input, tambahkan validasi untuk foto
$request->validate([
'nama_lengkap' => 'required|string|max:255',
'no_hp' => 'required|numeric',
'tempat_lahir' => 'required|string',
'tgl_lahir' => 'required|date',
'jenis_kelamin' => 'required|in:L,P',
'alamat' => 'required|string',
'foto_profil' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', // Maksimal 2MB
]);
// 2. Ambil data teks
$input = $request->only([
'nama_lengkap',
'no_hp',
'tempat_lahir',
'tgl_lahir',
'jenis_kelamin',
'alamat'
]);
// 3. Logika Upload Foto
if ($request->hasFile('foto_profil')) {
// Jika anggota sudah punya foto sebelumnya, hapus file lamanya dari storage
if ($anggota->foto_profil && Storage::disk('public')->exists($anggota->foto_profil)) {
Storage::disk('public')->delete($anggota->foto_profil);
}
// Simpan foto baru ke folder 'profil_images' di dalam storage/app/public
$path = $request->file('foto_profil')->store('profil_images', 'public');
// Masukkan path foto baru ke dalam array input untuk disimpan ke database
$input['foto_profil'] = $path;
}
// 4. Update data ke database
$anggota->update($input);
// Update nama di tabel users agar sinkron
Auth::user()->update([
'name' => $request->nama_lengkap
]);
return redirect()->route('profile.edit')->with('success', 'Profil Anda berhasil diperbarui!');
}
/**
* Menyimpan perubahan password (FUNGSI BARU).
*/
public function updatePassword(Request $request)
{
// 1. Validasi input
// 'confirmed' akan otomatis mencocokkan input 'password' dengan 'password_confirmation'
$request->validate([
'current_password' => 'required',
'password' => 'required|min:8|confirmed',
], [
'password.confirmed' => 'Konfirmasi password baru tidak cocok!',
'password.min' => 'Password minimal harus 8 karakter!'
]);
$user = Auth::user();
// 2. Cek apakah password lama yang dimasukkan benar
if (!Hash::check($request->current_password, $user->password)) {
// Jika salah, kembalikan ke halaman profil dengan pesan error
return back()->withErrors(['current_password' => 'Password saat ini salah.']);
}
// 3. Jika benar, update dengan password baru yang di-hash (enkripsi)
$user->update([
'password' => Hash::make($request->password)
]);
return redirect()->route('profile.edit')->with('success', 'Password berhasil diperbarui!');
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers;
use App\Models\Anggota;
use App\Models\Kolat;
use Illuminate\Http\Request;
class RekapController extends Controller
{
public function index(Request $request) // Tambahkan Request untuk filter
{
// 1. Ambil data Kolat untuk dropdown filter
$data_kolat = Kolat::all();
// 2. Query data anggota dengan filter
// $query = Anggota::with(['presensi', 'spp', 'kolat']);
$query = Anggota::with(['presensi', 'spp', 'kolatLatihan']);
if ($request->kolat_id) {
$query->where('kolat_id', $request->kolat_id);
}
if ($request->status) {
$query->where('status', $request->status);
}
$anggota = $query->get();
// 3. Transformasi data untuk Rekap
$dataRekap = $anggota->map(function ($item) {
// Logika Presensi
$totalPertemuan = $item->presensi->count();
$hadir = $item->presensi->where('status', 'Hadir')->count();
$persentasePresensi = $totalPertemuan > 0 ? ($hadir / $totalPertemuan) * 100 : 0;
// Logika SPP
$punyaTunggakan = $item->spp->where('status', 'belum_lunas')->count() > 0;
// Logika Keaktifan
$statusAktif = ($totalPertemuan > 0 && $persentasePresensi > 0) ? 'Aktif' : 'Non-Aktif';
// Syarat Ujian (Presensi >= 75% dan SPP Lunas)
$layakUjian = ($persentasePresensi >= 75 && !$punyaTunggakan);
return [
'nama' => $item->nama_lengkap, // Sesuaikan dengan kolom tabel Anda
'no_induk' => $item->no_induk,
'tingkatan' => $item->tingkatan,
// 'nama_kolat' => $item->kolat->nama_kolat ?? '-',
'nama_kolat' => $item->kolatLatihan->pluck('nama_kolat')->join(', '),
'status_aktif' => $statusAktif,
'persentase_presensi' => round($persentasePresensi, 1),
'status_spp' => $punyaTunggakan ? 'Ada Tunggakan' : 'Lunas',
'layak' => $layakUjian
];
});
// 4. Kirim variabel yang dibutuhkan view
return view('rekap.index', compact('dataRekap', 'data_kolat'));
}
}

View File

@ -0,0 +1,350 @@
<?php
namespace App\Http\Controllers;
use App\Exports\SppExport;
use App\Models\Anggota;
use App\Models\Kas;
use App\Models\Spp;
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
class SppController extends Controller
{
public function index(Request $request)
{
$query = Spp::with(['anggota', 'kolat']);
// Filter
if ($request->filled('status')) {
$query->where('status', $request->status);
}
if ($request->filled('bulan')) {
$query->where('bulan', $request->bulan);
}
if ($request->filled('tahun')) {
$query->where('tahun', $request->tahun);
}
if ($request->filled('cari')) {
$query->whereHas('anggota', function ($q) use ($request) {
$q->where('nama', 'like', '%'.$request->cari.'%');
});
}
$data_spp = $query->orderBy('tahun', 'desc')
->orderBy('bulan', 'desc')
->get();
$stats = [
'lunas' => Spp::where('status', 'lunas')->count(),
'pending' => Spp::where('status', 'pending')->count(),
'belum_bayar' => Spp::where('status', 'belum_bayar')->count(),
'total_pemasukan' => Spp::where('status', 'lunas')->sum('nominal'),
];
return view('spp.pengurus.index', compact('data_spp', 'stats'));
}
public function create()
{
// Pastikan nama kolom di database adalah 'nama', bukan 'nama_lengkap' (sesuai diskusi sebelumnya)
$data_anggota = Anggota::orderBy('nama_lengkap', 'asc')->get();
return view('spp.pengurus.create', compact('data_anggota'));
}
public function store(Request $request)
{
// 1. Validasi Input
$request->validate([
'anggota_id' => 'required|exists:anggotas,id',
'bulan' => 'required',
'tahun' => 'required',
'nominal' => 'required|numeric',
'jatuh_tempo' => 'required|date',
]);
// 2. PENJAGA: Cek apakah tagihan untuk anggota, bulan, dan tahun tersebut sudah ada
$cekDuplikat = Spp::where('anggota_id', $request->anggota_id)
->where('bulan', $request->bulan)
->where('tahun', $request->tahun)
->exists();
// Jika sudah ada, langsung tendang balik ke form dengan pesan error
if ($cekDuplikat) {
return redirect()->back()
->withInput() // withInput menjaga agar form tidak kosong lagi
->with('error', "Gagal! Tagihan untuk bulan {$request->bulan} {$request->tahun} sudah terdaftar untuk anggota ini.");
}
// 3. Proses Penyimpanan (Jika lolos penjagaan)
try {
$anggota = Anggota::findOrFail($request->anggota_id);
Spp::create([
'anggota_id' => $request->anggota_id,
'kolat_id' => $anggota->kolat_id,
'bulan' => $request->bulan,
'tahun' => $request->tahun,
'nominal' => $request->nominal,
'jatuh_tempo' => $request->jatuh_tempo,
// Catatan Kecil: Pastikan penulisan 'belum_bayar' ini sama persis
// dengan filter yang dipakai di Dashboard Anggota ya!
'status' => 'pending',
]);
return redirect()->route('spp.index')->with('success', 'Tagihan manual berhasil dibuat!');
} catch (\Exception $e) {
return redirect()->back()->withInput()->with('error', 'Gagal menyimpan: '.$e->getMessage());
}
}
public function bayar(Request $request, $id)
{
$spp = Spp::with('anggota')->findOrFail($id);
if ($spp->status == 'lunas') {
return redirect()->back()->with('error', 'Tagihan ini sudah lunas.');
}
DB::beginTransaction();
try {
$saldo_terakhir = Kas::saldoTerakhir() ?? 0;
$saldo_baru = $saldo_terakhir + $spp->nominal;
// Buat Catatan Kas
$kas = Kas::create([
'tanggal' => now(),
'jenis' => 'masuk',
'kategori' => 'SPP',
'nominal' => $spp->nominal,
'keterangan' => 'Bayar SPP ' . $spp->anggota->nama_lengkap . ' - ' . $spp->bulan . ' ' . $spp->tahun,
'saldo_akhir' => $saldo_baru,
]);
// Update Status SPP
$spp->update([
'status' => 'lunas',
// Jangan timpa tanggal_bayar jika sudah diisi saat upload bukti TF
'tanggal_bayar' => $spp->tanggal_bayar ?? now(),
'kas_id' => $kas->id,
]);
DB::commit();
$pesan = $spp->status == 'pending' ? 'Pembayaran via transfer berhasil divalidasi!' : 'Pembayaran SPP tunai berhasil dicatat!';
return redirect()->back()->with('success', $pesan);
} catch (\Exception $e) {
DB::rollback();
return redirect()->back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
}
}
public function generateTagihan(Request $request)
{
// PERBAIKAN BHS INDONESIA: Tambahkan ->locale('id') agar outputnya pasti "Juni"
$bulanIni = \Carbon\Carbon::now()->locale('id')->translatedFormat('F');
$tahunIni = \Carbon\Carbon::now()->year;
// Sesuaikan format jatuh tempo ke Y-m-d agar aman saat masuk ke database
$jatuhTempo = \Carbon\Carbon::now()->day(10)->format('Y-m-d');
// --- 1. PENJAGA: Cek apakah bulan dan tahun ini sudah pernah di-generate ---
$cekSudahGenerate = Spp::where('bulan', $bulanIni)
->where('tahun', $tahunIni)
->exists();
if ($cekSudahGenerate) {
// Jika sudah ada, langsung tolak dan kembalikan ke halaman index dengan pesan warning
return redirect()->route('spp.index')->with('warning', "Tagihan SPP bulan {$bulanIni} {$tahunIni} sudah pernah dibuat!");
}
// --------------------------------------------------------------------------
$anggota = Anggota::where('status', 'Aktif')->get(); // Pastikan hanya menagih yang aktif
DB::beginTransaction();
try {
foreach ($anggota as $agt) {
// Pengecekan per-user untuk keamanan ekstra
$exists = Spp::where('anggota_id', $agt->id)
->where('bulan', $bulanIni)
->where('tahun', $tahunIni)
->exists();
if (! $exists) {
Spp::create([
'anggota_id' => $agt->id,
'kolat_id' => $agt->kolat_id,
'bulan' => $bulanIni,
'tahun' => $tahunIni,
'nominal' => 50000,
'jatuh_tempo' => $jatuhTempo,
'status' => 'belum_bayar', // PERBAIKAN STATUS: Disamakan menjadi 'belum_bayar'
]);
}
}
DB::commit();
return redirect()->route('spp.index')->with('success', "Tagihan bulan {$bulanIni} berhasil dibuat!");
} catch (\Exception $e) {
DB::rollback();
return redirect()->back()->with('error', 'Gagal: '.$e->getMessage());
}
}
public function exportExcel(Request $request)
{
$query = Spp::with(['anggota', 'kolat']);
// Terapkan filter yang sama dengan index
if ($request->filled('status')) {
$query->where('status', $request->status);
}
if ($request->filled('bulan')) {
$query->where('bulan', $request->bulan);
}
if ($request->filled('tahun')) {
$query->where('tahun', $request->tahun);
}
if ($request->filled('cari')) {
$query->whereHas('anggota', function ($q) use ($request) {
$q->where('nama_lengkap', 'like', '%'.$request->cari.'%');
});
}
$data = $query->orderBy('tahun', 'desc')->orderBy('bulan', 'desc')->get();
return Excel::download(new SppExport($data), 'Laporan_SPP_TapakMP_'.date('Ymd').'.xlsx');
}
public function exportPdf(Request $request)
{
$query = Spp::with(['anggota', 'kolat']);
if ($request->filled('status')) {
$query->where('status', $request->status);
}
if ($request->filled('bulan')) {
$query->where('bulan', $request->bulan);
}
if ($request->filled('tahun')) {
$query->where('tahun', $request->tahun);
}
if ($request->filled('cari')) {
$query->whereHas('anggota', function ($q) use ($request) {
$q->where('nama_lengkap', 'like', '%'.$request->cari.'%');
});
}
$data_spp = $query->orderBy('tahun', 'desc')->orderBy('bulan', 'desc')->get();
$pdf = Pdf::loadView('spp.pengurus.export_pdf', compact('data_spp'));
$pdf->setPaper('a4', 'landscape'); // Landscape agar tabel lega
return $pdf->download('Laporan_SPP_TapakMP_'.date('Ymd').'.pdf');
}
// public function indexAnggota()
// {
// // Sesuaikan cara pemanggilan ID anggota dengan struktur tabel User kamu
// // Contoh ini mengasumsikan di tabel users ada kolom 'anggota_id'
// $anggota_id = auth()->user()->anggota_id;
// $data_spp = Spp::where('anggota_id', $anggota_id)
// ->orderBy('tahun', 'desc')
// ->orderBy('bulan', 'desc')
// ->get();
// return view('spp.anggota.index', compact('data_spp'));
// }
public function indexAnggota()
{
// 1. Panggil relasi anggota dari user yang sedang login
$profil_anggota = auth()->user()->anggota;
// 2. Proteksi jika user belum punya profil anggota (mencegah error)
if (!$profil_anggota) {
$data_spp = collect(); // Kirim data kosong agar tidak error di view
return view('spp.anggota.index', compact('data_spp'))
->with('error', 'Profil anggota Anda belum terhubung. Silakan hubungi pengurus.');
}
// 3. Jika aman, ambil ID anggotanya
$anggota_id = $profil_anggota->id;
// 4. Cari tagihan SPP
$data_spp = Spp::where('anggota_id', $anggota_id)
->orderBy('tahun', 'desc')
->orderBy('bulan', 'desc')
->get();
return view('spp.anggota.index', compact('data_spp'));
}
// 2. Menampilkan form upload bukti transfer
// public function formBayarAnggota($id)
// {
// $spp = Spp::findOrFail($id);
// // Keamanan: Pastikan yang diakses adalah tagihannya sendiri
// if ($spp->anggota_id != auth()->user()->anggota_id) {
// abort(403, 'Akses ditolak.');
// }
// return view('spp.anggota.bayar', compact('spp'));
// }
public function formBayarAnggota($id)
{
$spp = Spp::findOrFail($id);
$profil_anggota = auth()->user()->anggota;
// Proteksi 1: Pastikan user punya profil anggota
if (!$profil_anggota) {
abort(403, 'Profil anggota belum terhubung.');
}
// Proteksi 2: Pastikan yang diakses adalah tagihannya sendiri
if ($spp->anggota_id != $profil_anggota->id) {
abort(403, 'Akses ditolak. Ini bukan tagihan Anda.');
}
return view('spp.anggota.bayar', compact('spp'));
}
// 3. Memproses upload gambar bukti TF
public function prosesBayarAnggota(Request $request, $id)
{
$request->validate([
'bukti_pembayaran' => 'required|image|mimes:jpeg,png,jpg|max:2048', // Maksimal 2MB
], [
'bukti_pembayaran.required' => 'Bukti transfer wajib diunggah.',
'bukti_pembayaran.image' => 'File harus berupa gambar.',
]);
$spp = Spp::findOrFail($id);
if ($request->hasFile('bukti_pembayaran')) {
$file = $request->file('bukti_pembayaran');
// Membuat nama file unik
$nama_file = time() . "_" . $file->getClientOriginalName();
// Pindahkan gambar ke folder public/uploads/bukti_spp
$file->move(public_path('uploads/bukti_spp'), $nama_file);
// Update status menjadi pending dan simpan nama file
$spp->update([
'bukti_pembayaran' => $nama_file,
'status' => 'pending',
'tanggal_bayar' => now(), // Mencatat waktu upload
]);
return redirect()->route('spp.anggota.index')->with('success', 'Bukti pembayaran berhasil diunggah! Menunggu konfirmasi pengurus.');
}
return redirect()->back()->with('error', 'Gagal mengunggah bukti pembayaran.');
}
}

View File

@ -0,0 +1,125 @@
// controller jadwal //
<?php
namespace App\Http\Controllers;
use App\Models\Jadwal;
use App\Models\Kolat;
use App\Models\Anggota;
use App\Models\Role;
use Illuminate\Http\Request;
class JadwalController extends Controller
{
public function index()
{
$data_jadwal = Jadwal::with(['kolat', 'pelatih'])
->orderBy('tanggal', 'desc')
->orderBy('jam_mulai', 'desc')
->get();
return view('jadwal.jadwal', compact('data_jadwal'));
}
public function konfirmasiSelesai($id)
{
$jadwal = Jadwal::findOrFail($id);
// Kita paksa ubah status ke 'selesai'
$jadwal->status = 'selesai';
$jadwal->save();
return redirect()->route('jadwal.index')
->with('success', 'Latihan ' . $jadwal->judul_kegiatan . ' telah dikonfirmasi selesai!');
}
public function create()
{
$data_kolat = Kolat::all();
$rolePelatih = Role::where('nama_role', 'Pelatih')->first();
// Ambil Anggota yang memiliki role 'Pelatih' saja (filter lewat relasi)
$data_pelatih = Anggota::whereHas('role', function($query) {
$query->where('nama_role', 'Pelatih');
})
->where('status', 'Aktif') // <-- INI BATASAN TAMBAHANNYA
->orderBy('nama_lengkap', 'asc') // (Bonus) Biar namanya urut abjad di form
->get();
return view('jadwal.jadwalcreate', compact('data_kolat', 'data_pelatih'));
}
public function store(Request $request)
{
$request->validate([
'judul_kegiatan' => 'required|string|max:255',
'kolat_id' => 'required|exists:kolat,id',
'pelatih_id' => 'required|exists:anggotas,id',
'jenis' => 'required|in:Rutin,Tambahan,Pengumuman',
'tanggal' => 'required|date',
'jam_mulai' => 'required',
'lokasi' => 'required|string',
]);
Jadwal::create($request->all());
return redirect()->route('jadwal.index')
->with('success', 'Jadwal latihan berhasil ditambahkan!');
}
public function show($id)
{
// Ambil satu data dengan relasinya
$jadwal = Jadwal::with(['kolat', 'pelatih'])->findOrFail($id);
return view('jadwal.show', compact('jadwal'));
}
public function edit($id)
{
$jadwal = Jadwal::findOrFail($id);
$data_kolat = Kolat::all();
// Tetap filter agar hanya muncul daftar pelatih
$data_pelatih = Anggota::whereHas('role', function($query) {
$query->where('nama_role', 'Pelatih');
})->get();
return view('jadwal.editjadwal', compact('jadwal', 'data_kolat', 'data_pelatih'));
}
public function update(Request $request, $id)
{
$jadwal = Jadwal::findOrFail($id);
$request->validate([
'judul_kegiatan' => 'required|string|max:255',
'kolat_id' => 'required|exists:kolat,id',
'pelatih_id' => 'required|exists:anggotas,id',
'jenis' => 'required|in:Rutin,Tambahan,Pengumuman',
'tanggal' => 'required|date',
'jam_mulai' => 'required',
'lokasi' => 'required|string',
]);
$jadwal->update($request->all());
return redirect()->route('jadwal.index')
->with('success', 'Jadwal berhasil diperbarui!');
}
public function destroy($id)
{
$jadwal = Jadwal::findOrFail($id);
$jadwal->delete();
return redirect()->route('jadwal.index')
->with('success', 'Jadwal telah dihapus dari sistem.');
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's middleware aliases.
*
* Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
*
* @var array<string, class-string|string>
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'checkrole' => \App\Http\Middleware\CheckRole::class,
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*/
protected function redirectTo(Request $request): ?string
{
return $request->expectsJson() ? null : route('login');
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class CheckRole
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, ...$roles): Response
{
// // 1. Pastikan user sudah login
// if (!Auth::check()) {
// return redirect()->route('login');
// }
// $user = Auth::user();
// $namaRole = null;
// // 2. Ambil nama_role dari relasi.
// if ($user->role) {
// $namaRole = $user->role->nama_role;
// } elseif ($user->anggota && $user->anggota->role) {
// $namaRole = $user->anggota->role->nama_role;
// }
// // ==========================================
// // LETAKKAN KODE DD DI SINI (UNTUK DEBUGGING)
// // ==========================================
// // dd('Role di DB: ' . $namaRole, 'Role di Rute: ', $roles);
// // 3. Cek apakah nama_role user ada di dalam daftar role
// if (in_array($namaRole, $roles)) {
// return $next($request);
// }
// // 4. Jika role tidak cocok, tolak akses
// abort(403, 'Akses Ditolak. Anda tidak memiliki wewenang untuk mengakses halaman ini.');
// baru
// 1. Pastikan user sudah login
if (!Auth::check()) {
return redirect()->route('login');
}
$user = Auth::user();
// 2. LOGIKA BARU: Pengecekan Banyak Peran (Multiple Roles)
if ($user->anggota) {
// Looping daftar role yang diizinkan oleh Route (misal: 'Pengurus', 'Pelatih')
foreach ($roles as $role) {
// Jika user yang sedang login punya salah satu role tersebut, langsung loloskan!
if ($user->anggota->roles->contains('nama_role', $role)) {
return $next($request);
}
}
}
// 3. Jika tidak ada satupun role yang cocok, tolak akses
abort(403, 'Akses Ditolak. Anda tidak memiliki wewenang untuk mengakses halaman ini.');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string ...$guards): Response
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
// Ambil data user yang sedang login
$user = Auth::guard($guard)->user();
$roleName = null;
// Cek nama role sesuai struktur relasi TapakMP
if ($user->role) {
$roleName = $user->role->nama_role;
} elseif ($user->anggota && $user->anggota->role) {
$roleName = $user->anggota->role->nama_role;
}
// Arahkan ke dashboard yang sesuai
if ($roleName === 'Anggota') {
return redirect()->route('anggota.dashboard');
}
if ($roleName === 'Pelatih' || $roleName === 'Pengurus') {
return redirect()->route('dashboard');
}
// Default bawaan Laravel jika role tidak terdefinisi
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array<int, string>
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustHosts as Middleware;
class TrustHosts extends Middleware
{
/**
* Get the host patterns that should be trusted.
*
* @return array<int, string|null>
*/
public function hosts(): array
{
return [
$this->allSubdomainsOfApplicationUrl(),
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array<int, string>|string|null
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
class ValidateSignature extends Middleware
{
/**
* The names of the query string parameters that should be ignored.
*
* @var array<int, string>
*/
protected $except = [
// 'fbclid',
// 'utm_campaign',
// 'utm_content',
// 'utm_medium',
// 'utm_source',
// 'utm_term',
];
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,104 @@
<?php
namespace App\Models;
use App\Models\Role;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Anggota extends Model
{
use HasFactory;
protected $table = 'anggotas';
protected $fillable = [
'user_id', 'no_induk', 'nama_lengkap', 'foto_profil', 'jenis_kelamin',
'tempat_lahir', 'tgl_lahir', 'no_hp', 'kolat_id',
'tingkatan', 'tgl_gabung', 'status', 'alamat',
'catatan_medis', 'jabatan', 'no_sk', 'masa_berlaku', 'foto_sk'
];
public function kolat()
{
// Pastikan file Kolat.php juga ada di folder Models
return $this->belongsTo(Kolat::class, 'kolat_id');
}
// public function role()
// {
// return $this->belongsTo(Role::class, 'role_id');
// }
// TAMBAHKAN relasi baru ini:
public function roles()
{
// Menghubungkan Anggota ke Role lewat tabel pivot anggota_role
return $this->belongsToMany(Role::class, 'anggota_role', 'anggota_id', 'role_id')->withTimestamps();
}
/**
* Fungsi bantuan (Helper) untuk mengecek apakah anggota memiliki role tertentu.
* Sangat berguna nanti di Controller, Middleware, atau Blade.
*/
public function hasRole($roleName)
{
return $this->roles()->where('nama_role', $roleName)->exists();
}
public function kolatLatihan()
{
return $this->belongsToMany(Kolat::class, 'kolat_pelatih', 'anggota_id', 'kolat_id');
}
// Scope agar gampang memanggil data yang hanya ber-role Pelatih
// public function scopeIsPelatih($query)
// {
// return $query->whereHas('role', function ($q) {
// $q->where('nama_role', 'Pelatih');
// });
// }
// KODE BARU (Ubah jadi 'roles')
public function scopeIsPelatih($query)
{
return $query->whereHas('roles', function ($q) { // <-- Ubah 'role' jadi 'roles'
$q->where('nama_role', 'Pelatih');
});
}
// Relasi: Mendapatkan riwayat kehadiran anggota
public function riwayatPresensi()
{
return $this->hasMany(Presensi::class, 'anggota_id');
}
// Relasi: Jika dia pelatih, mendapatkan semua jadwal tugasnya
public function jadwalMelatih()
{
return $this->hasMany(Jadwal::class, 'pelatih_id');
}
// Relasi: Jika dia pelatih, mendapatkan semua evaluasi dari murid
public function evaluasiMasuk()
{
return $this->hasMany(Penilaian::class, 'pelatih_id');
}
// Anggota.php
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
public function presensi() {
return $this->hasMany(Presensi::class);
}
public function spp() {
return $this->hasMany(Spp::class);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Models;
use App\Models\Presensi;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Jadwal extends Model
{
use HasFactory;
protected $fillable = [
'kolat_id',
'pelatih_id',
'judul_kegiatan',
'jenis',
'tanggal',
'jam_mulai',
'jam_selesai',
'lokasi',
'deskripsi',
'foto_bukti', // Tambahan baru untuk foto kegiatan (Pelatih)
'catatan_latihan',
];
// Relasi: Jadwal ini milik Kolat mana?
public function kolat()
{
return $this->belongsTo(Kolat::class, 'kolat_id');
}
// Relasi: Siapa pelatih yang bertugas di jadwal ini?
public function pelatih()
{
return $this->belongsTo(Anggota::class, 'pelatih_id');
}
// Relasi: Jadwal ini punya banyak data presensi anggota
public function presensi()
{
return $this->hasMany(Presensi::class, 'jadwal_id');
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Kas extends Model
{
use HasFactory;
protected $table = 'kas';
protected $fillable = [
'tanggal',
'jenis',
'kategori',
'nominal',
'keterangan',
'saldo_akhir'
];
/**
* Scope untuk mendapatkan saldo terakhir.
* Ini akan memudahkan kamu saat ingin menghitung saldo baru.
*/
public static function saldoTerakhir()
{
$last = self::orderBy('id', 'desc')->first();
return $last ? $last->saldo_akhir : 0;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Kolat extends Model
{
use HasFactory;
// Tentukan nama tabel jika tidak otomatis jamak
protected $table = 'kolat';
// Daftarkan kolom yang boleh diisi (Mass Assignment)
protected $fillable = [
'nama_kolat',
'alamat_kolat',
];
// Relasi: Satu Kolat memiliki banyak Anggota
public function anggota()
{
return $this->hasMany(Anggota::class, 'kolat_id');
}
public function pelatihs()
{
return $this->belongsToMany(Anggota::class, 'kolat_pelatih', 'kolat_id', 'anggota_id')->withTimestamps();
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Pelatih extends Model
{
protected $table = 'anggota';
// Kolom yang boleh diisi massal
protected $fillable = [
'anggota_id',
'no_sk',
'tgl_sk',
'masa_berlaku',
'foto_sk',
'status_pelatih',
'catatan'
];
public function anggota(): BelongsTo
{
return $this->belongsTo(Anggota::class, 'anggota_id');
}
// public function kolat(): BelongsTo
// {
// return $this->belongsTo(Kolat::class, 'kolat_id');
// }
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Penilaian extends Model
{
use HasFactory;
protected $fillable = [
'pelatih_id',
'anggota_id',
'bulan_evaluasi',
'metode_pelatihan',
'komunikasi',
'sikap_kepribadian',
'kepemimpinan',
'konsistensi_komitmen',
'kedekatan_interpersonal',
'kritik_saran'
];
// Relasi: Siapa pelatih yang sedang dinilai?
public function pelatih()
{
return $this->belongsTo(Anggota::class, 'pelatih_id');
}
// Relasi: Siapa anggota yang memberikan nilai (penilai)?
public function penilai()
{
return $this->belongsTo(Anggota::class, 'anggota_id');
}
public function show($id)
{
$pelatih = Anggota::with('kolat')->findOrFail($id);
// Hitung rata-rata tiap kriteria bulan ini
$rekap = Penilaian::where('pelatih_id', $id)
->whereMonth('bulan_evaluasi', now()->month)
->selectRaw('
AVG(metode_pelatihan) as avg_metode,
AVG(komunikasi) as avg_komunikasi,
AVG(sikap_kepribadian) as avg_sikap,
AVG(kepemimpinan) as avg_kepemimpinan,
AVG(konsistensi_komitmen) as avg_komitmen,
AVG(kedekatan_interpersonal) as avg_interpersonal
')->first();
// Ambil ulasan/kritik saran (Pluck agar anonim)
$ulasan = Penilaian::where('pelatih_id', $id)
->whereMonth('bulan_evaluasi', now()->month)
->whereNotNull('kritik_saran')
->pluck('kritik_saran');
return view('penilaian.rekap', compact('pelatih', 'rekap', 'ulasan'));
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Presensi extends Model
{
use HasFactory;
protected $fillable = [
'jadwal_id',
'anggota_id',
'status',
'keterangan',
'waktu_presensi',
'is_verified',
'verified_by'
];
// Relasi: Presensi ini untuk jadwal yang mana?
public function jadwal()
{
return $this->belongsTo(Jadwal::class, 'jadwal_id');
}
// Relasi: Siapa anggota yang melakukan presensi?
public function anggota()
{
return $this->belongsTo(Anggota::class, 'anggota_id');
}
public function pelatihVerifikator()
{
return $this->belongsTo(Anggota::class, 'verified_by');
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
use HasFactory;
protected $table = 'role'; // Pastikan nama tabelnya 'roles' di database
protected $fillable = ['nama_role']; // Sesuaikan kolom di tabel roles kamu
public function anggotas()
{
return $this->belongsToMany(Anggota::class, 'anggota_role', 'role_id', 'anggota_id')->withTimestamps();
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Spp extends Model
{
protected $table = 'spp';
protected $fillable = [
'anggota_id',
'kolat_id',
'kas_id',
'bulan',
'tahun',
'nominal',
'status',
'jatuh_tempo',
'keterangan',
'bukti_pembayaran'
];
// Relasi ke Anggota
public function anggota()
{
return $this->belongsTo(Anggota::class);
}
// Relasi ke Kolat
public function kolat()
{
return $this->belongsTo(Kolat::class);
}
// Relasi ke Kas
public function kas()
{
return $this->belongsTo(Kas::class);
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
public function anggota()
{
// Setiap User memiliki satu data profil Anggota
return $this->hasOne(Anggota::class, 'user_id');
}
// Relasi ke tabel Role
public function role()
{
return $this->belongsTo(Role::class, 'role_id');
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace App\Providers;
use App\Models\Anggota;
use App\Models\Jadwal;
use App\Models\Kas;
use App\Models\Spp;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
View::composer('*', function ($view) {
if (Auth::check()) {
$user = Auth::user();
$userRole = 'Anggota';
if ($user->anggota) {
if ($user->anggota->roles->contains('nama_role', 'Pengurus')) {
$userRole = 'Pengurus';
} elseif ($user->anggota->roles->contains('nama_role', 'Pelatih')) {
$userRole = 'Pelatih';
}
}// Sesuaikan dengan cara Anda ambil role
$notifications = [];
$unreadCount = 0;
// --- 1. LOGIKA NOTIFIKASI PENGURUS ---
if ($userRole == 'Pengurus') {
// A. Verifikasi SPP Pending
$sppPending = Spp::where('status', 'pending')->count();
if ($sppPending > 0) {
$notifications[] = [
'icon' => 'bi-cash-stack text-warning',
'title' => 'Verifikasi SPP',
'msg' => "Ada $sppPending bukti bayar menunggu validasi.",
'link' => route('spp.index')
];
}
// B. Verifikasi Anggota Baru (Pendaftar)
$anggotaBaru = Anggota::where('status', 'Pending')->count();
if ($anggotaBaru > 0) {
$notifications[] = [
'icon' => 'bi-person-plus text-primary',
'title' => 'Verifikasi Anggota',
'msg' => "Ada $anggotaBaru calon anggota baru perlu divalidasi.",
'link' => route('anggota.anggota')
];
}
// C. Kandidat Pelatih Baru (Kriteria: Usia >= 18 & Tingkatan Balik 1)
// Kita hitung menggunakan Carbon untuk tanggal lahir
// $kandidatPelatih = Anggota::where('tingkatan', 'Balik 1')
// ->where('status', 'Aktif')
// ->where('tgl_lahir', '<=', Carbon::now()->subYears(18))
// ->count();
// if ($kandidatPelatih > 0) {
// $notifications[] = [
// 'icon' => 'bi-star-fill text-info',
// 'title' => 'Kandidat Pelatih',
// 'msg' => "Ada $kandidatPelatih anggota Balik 1 berusia 18th siap jadi Pelatih.",
// 'link' => route('anggota.anggota') // Bisa diarahkan ke daftar anggota filter Balik 1
// ];
// }
// D. Peringatan Saldo Kas Kritis (< 500rb)
$saldo = Kas::sum('nominal');
if ($saldo < 500000) {
$notifications[] = [
'icon' => 'bi-exclamation-triangle text-danger',
'title' => 'Peringatan Saldo',
'msg' => "Saldo Kas Cabang kritis: Rp " . number_format($saldo, 0, ',', '.'),
'link' => route('kas.index')
];
}
}
// --- LOGIKA NOTIFIKASI PELATIH ---
if ($userRole == 'Pelatih') {
$jadwalHariIni = Jadwal::whereDate('tanggal', date('Y-m-d'))->count();
if ($jadwalHariIni > 0) {
$notifications[] = [
'icon' => 'bi-calendar-event text-primary',
'title' => 'Jadwal Latihan',
'message' => "Anda memiliki $jadwalHariIni jadwal hari ini.",
'link' => route('jadwal.index')
];
$unreadCount += 1;
}
}
// --- LOGIKA NOTIFIKASI ANGGOTA ---
if ($userRole == 'Anggota') {
$sppSaya = Spp::where('anggota_id', $user->anggota->id)
->where('status', 'belum_lunas')->count();
if ($sppSaya > 0) {
$notifications[] = [
'icon' => 'bi-cash-stack text-danger',
'title' => 'Tagihan SPP',
'message' => "Anda memiliki $sppSaya tagihan belum terbayar.",
'link' => route('spp.anggota.index')
];
$unreadCount += 1;
}
}
// Kirim data ke SEMUA View
$view->with('globalNotifications', $notifications);
$view->with('globalUnreadCount', $unreadCount);
}
});
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Providers;
// use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [
//
];
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
//
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event to listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];
/**
* Register any events for your application.
*/
public function boot(): void
{
//
}
/**
* Determine if events and listeners should be automatically discovered.
*/
public function shouldDiscoverEvents(): bool
{
return false;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Providers;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* The path to your application's "home" route.
*
* Typically, users are redirected here after authentication.
*
* @var string
*/
public const HOME = '/home';
/**
* Define your route model bindings, pattern filters, and other route configuration.
*/
public function boot(): void
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
$this->routes(function () {
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
});
}
}

53
manajemen_mp/artisan Normal file
View File

@ -0,0 +1,53 @@
#!/usr/bin/env php
<?php
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any of our classes manually. It's great to relax.
|
*/
require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
|
| When we run the console application, the current CLI command will be
| executed in this console and the response sent back to a terminal
| or another output device for the developers. Here goes nothing!
|
*/
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
/*
|--------------------------------------------------------------------------
| Shutdown The Application
|--------------------------------------------------------------------------
|
| Once Artisan has finished running, we will fire off the shutdown events
| so that any final work may be done by the application before we shut
| down the process. This is the last thing to happen to the request.
|
*/
$kernel->terminate($input, $status);
exit($status);

View File

@ -0,0 +1,55 @@
<?php
/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/
return $app;

View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -0,0 +1,69 @@
{
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.1",
"barryvdh/laravel-dompdf": "^3.1",
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.3",
"laravel/tinker": "^2.8",
"maatwebsite/excel": "^3.1",
"simplesoftwareio/simple-qrcode": "^4.2"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
"laravel/pint": "^1.0",
"laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^7.0",
"phpunit/phpunit": "^10.1",
"spatie/laravel-ignition": "^2.0"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

9490
manajemen_mp/composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

189
manajemen_mp/config/app.php Normal file
View File

@ -0,0 +1,189 @@
<?php
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\ServiceProvider;
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| any other location as required by the application or its packages.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
*/
'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => 'Asia/Jakarta',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => 'en',
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'fallback_locale' => 'en',
/*
|--------------------------------------------------------------------------
| Faker Locale
|--------------------------------------------------------------------------
|
| This locale will be used by the Faker PHP library when generating fake
| data for your database seeds. For example, this will be used to get
| localized telephone numbers, street address information and more.
|
*/
'faker_locale' => 'en_US',
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => 'file',
// 'store' => 'redis',
],
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => ServiceProvider::defaultProviders()->merge([
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
])->toArray(),
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
'aliases' => Facade::defaultAliases()->merge([
// 'Example' => App\Facades\Example::class,
'QrCode' => SimpleSoftwareIO\QrCode\Facades\QrCode::class,
])->toArray(),
];

View File

@ -0,0 +1,115 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_reset_tokens',
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the amount of seconds before a password confirmation
| times out and the user is prompted to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => 10800,
];

View File

@ -0,0 +1,71 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "ably", "redis", "log", "null"
|
*/
'default' => env('BROADCAST_DRIVER', 'null'),
/*
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over websockets. Samples of
| each available type of connection are provided inside this array.
|
*/
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
'port' => env('PUSHER_PORT', 443),
'scheme' => env('PUSHER_SCHEME', 'https'),
'encrypted' => true,
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
],
'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
],
],
'ably' => [
'driver' => 'ably',
'key' => env('ABLY_KEY'),
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
],
'log' => [
'driver' => 'log',
],
'null' => [
'driver' => 'null',
],
],
];

View File

@ -0,0 +1,111 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache connection that gets used while
| using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function.
|
*/
'default' => env('CACHE_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb", "octane", "null"
|
*/
'stores' => [
'apc' => [
'driver' => 'apc',
],
'array' => [
'driver' => 'array',
'serialize' => false,
],
'database' => [
'driver' => 'database',
'table' => 'cache',
'connection' => null,
'lock_connection' => null,
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'lock_connection' => 'default',
],
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing the APC, database, memcached, Redis, or DynamoDB cache
| stores there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
];

View File

@ -0,0 +1,34 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];

View File

@ -0,0 +1,151 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for all database work. Of course
| you may use many connections at once using the Database library.
|
*/
'default' => env('DB_CONNECTION', 'mysql'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Here are each of the database connections setup for your application.
| Of course, examples of configuring each database platform that is
| supported by Laravel is shown below to make development simple.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DATABASE_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => 'prefer',
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run in the database.
|
*/
'migrations' => 'migrations',
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
];

View File

@ -0,0 +1,76 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application. Just store away!
|
*/
'default' => env('FILESYSTEM_DISK', 'local'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Here you may configure as many filesystem "disks" as you wish, and you
| may even configure multiple disks of the same driver. Defaults have
| been set up for each driver as an example of the required values.
|
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'throw' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
],
],
/*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
];

View File

@ -0,0 +1,54 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Hash Driver
|--------------------------------------------------------------------------
|
| This option controls the default hash driver that will be used to hash
| passwords for your application. By default, the bcrypt algorithm is
| used; however, you remain free to modify this option if you wish.
|
| Supported: "bcrypt", "argon", "argon2id"
|
*/
'driver' => 'bcrypt',
/*
|--------------------------------------------------------------------------
| Bcrypt Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Bcrypt algorithm. This will allow you
| to control the amount of time it takes to hash the given password.
|
*/
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 12),
'verify' => true,
],
/*
|--------------------------------------------------------------------------
| Argon Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Argon algorithm. These will allow you
| to control the amount of time it takes to hash the given password.
|
*/
'argon' => [
'memory' => 65536,
'threads' => 1,
'time' => 4,
'verify' => true,
],
];

View File

@ -0,0 +1,131 @@
<?php
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Deprecations Log Channel
|--------------------------------------------------------------------------
|
| This option controls the log channel that should be used to log warnings
| regarding deprecated PHP and library features. This allows you to get
| your application ready for upcoming major versions of dependencies.
|
*/
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => false,
],
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog",
| "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => LOG_USER,
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];

View File

@ -0,0 +1,134 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send any email
| messages sent by your application. Alternative mailers may be setup
| and used as needed; however, this mailer will be used by default.
|
*/
'default' => env('MAIL_MAILER', 'smtp'),
/*
|--------------------------------------------------------------------------
| Mailer Configurations
|--------------------------------------------------------------------------
|
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers to be used while
| sending an e-mail. You will specify which one you are using for your
| mailers below. You are free to add additional mailers as required.
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
| "postmark", "log", "array", "failover", "roundrobin"
|
*/
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
'port' => env('MAIL_PORT', 587),
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN'),
],
'ses' => [
'transport' => 'ses',
],
'postmark' => [
'transport' => 'postmark',
// 'message_stream_id' => null,
// 'client' => [
// 'timeout' => 5,
// ],
],
'mailgun' => [
'transport' => 'mailgun',
// 'client' => [
// 'timeout' => 5,
// ],
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
],
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
],
],
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all e-mails sent by your application to be sent from
| the same address. Here, you may specify a name and address that is
| used globally for all e-mails that are sent by your application.
|
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
/*
|--------------------------------------------------------------------------
| Markdown Mail Settings
|--------------------------------------------------------------------------
|
| If you are using Markdown based email rendering, you may configure your
| theme and component paths here, allowing you to customize the design
| of the emails. Or, you may simply stick with the Laravel defaults!
|
*/
'markdown' => [
'theme' => 'default',
'paths' => [
resource_path('views/vendor/mail'),
],
],
];

View File

@ -0,0 +1,109 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue API supports an assortment of back-ends via a single
| API, giving you convenient access to each back-end using the same
| syntax for every one. Here you may define a default connection.
|
*/
'default' => env('QUEUE_CONNECTION', 'sync'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection information for each server that
| is used by your application. A default configuration has been added
| for each back-end shipped with Laravel. You are free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'retry_after' => 90,
'after_commit' => false,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => 'localhost',
'queue' => 'default',
'retry_after' => 90,
'block_for' => 0,
'after_commit' => false,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
'after_commit' => false,
],
],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'mysql'),
'table' => 'job_batches',
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control which database and table are used to store the jobs that
| have failed. You may change them to any database / table you wish.
|
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'mysql'),
'table' => 'failed_jobs',
],
];

View File

@ -0,0 +1,83 @@
<?php
use Laravel\Sanctum\Sanctum;
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort()
))),
/*
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
*/
'guard' => ['web'],
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. This will override any values set in the token's
| "expires_at" attribute, but first-party sessions are not affected.
|
*/
'expiration' => null,
/*
|--------------------------------------------------------------------------
| Token Prefix
|--------------------------------------------------------------------------
|
| Sanctum can prefix new tokens in order to take advantage of numerous
| security scanning initiatives maintained by open source platforms
| that notify developers if they commit tokens into repositories.
|
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
*/
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
],
];

View File

@ -0,0 +1,34 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
'scheme' => 'https',
],
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
];

View File

@ -0,0 +1,214 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option controls the default session "driver" that will be used on
| requests. By default, we will use the lightweight native driver but
| you may specify any of the other wonderful drivers provided here.
|
| Supported: "file", "cookie", "database", "apc",
| "memcached", "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to immediately expire on the browser closing, set that option.
|
*/
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => false,
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it is stored. All encryption will be run
| automatically by Laravel and you can use the Session like normal.
|
*/
'encrypt' => false,
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When using the native session driver, we need a location where session
| files may be stored. A default has been set for you but a different
| location may be specified. This is only needed for file sessions.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table we
| should use to manage the sessions. Of course, a sensible default is
| provided for you; however, you are free to change this as needed.
|
*/
'table' => 'sessions',
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| While using one of the framework's cache driven session backends you may
| list a cache store that should be used for these sessions. This value
| must match with one of the application's configured cache "stores".
|
| Affects: "apc", "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the cookie used to identify a session
| instance by ID. The name specified here will get used every time a
| new session cookie is created by the framework for every driver.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application but you are free to change this when necessary.
|
*/
'path' => '/',
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| Here you may change the domain of the cookie used to identify a session
| in your application. This will determine which domains the cookie is
| available to in your application. A sensible default has been set.
|
*/
'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. You are free to modify this option if needed.
|
*/
'http_only' => true,
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" since this is a secure default value.
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => 'lax',
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => false,
];

View File

@ -0,0 +1,36 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| View Storage Paths
|--------------------------------------------------------------------------
|
| Most templating systems load templates from disk. Here you may specify
| an array of paths that should be checked for your views. Of course
| the usual Laravel view path has already been registered for you.
|
*/
'paths' => [
resource_path('views'),
],
/*
|--------------------------------------------------------------------------
| Compiled View Path
|--------------------------------------------------------------------------
|
| This option determines where all the compiled Blade templates will be
| stored for your application. Typically, this is within the storage
| directory. However, as usual, you are free to change this value.
|
*/
'compiled' => env(
'VIEW_COMPILED_PATH',
realpath(storage_path('framework/views'))
),
];

1
manajemen_mp/database/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.sqlite*

View File

@ -0,0 +1,44 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* The current password being used by the factory.
*/
protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}

View File

@ -0,0 +1,32 @@
<?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('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
}
};

View File

@ -0,0 +1,28 @@
<?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('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('password_reset_tokens');
}
};

View File

@ -0,0 +1,32 @@
<?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('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('failed_jobs');
}
};

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('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};

View File

@ -0,0 +1,29 @@
<?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('role', function (Blueprint $table) {
$table->id();
$table->string('nama_role');
$table->string('slug')->unique();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('role');
}
};

View File

@ -0,0 +1,29 @@
<?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('kolat', function (Blueprint $table) {
$table->id();
$table->string('nama_kolat');
$table->text('alamat_kolat')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('kolat');
}
};

View File

@ -0,0 +1,58 @@
<?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('anggotas', function (Blueprint $table) {
$table->id();
// --- RELASI (Penghubung ke Dropdown) ---
// $table->foreignId('role_id')->constrained('role')->onDelete('cascade');
$table->foreignId('kolat_id')->constrained('kolat')->onDelete('cascade');
// --- DATA IDENTITAS ---
$table->string('no_induk')->unique();
$table->string('nama_lengkap');
$table->enum('jenis_kelamin', ['L', 'P']);
$table->string('tempat_lahir');
$table->date('tgl_lahir');
// --- DATA KONTAK & ALAMAT ---
$table->string('no_hp', 15)->nullable();
$table->text('alamat');
// --- DATA ORGANISASI ---
$table->string('tingkatan'); // Dasar I, Balik II, dll
$table->date('tgl_gabung');
$table->date('tgl_keluar')->nullable(); // Diisi jika sudah tidak aktif
$table->enum('status', ['Aktif', 'Non-Aktif', 'Alumni'])->default('Aktif');
$table->string('jabatan')->default('Anggota'); // Jabatan struktural di kolat/cabang
// --- DATA KHUSUS KEPELATIHAN (Nullable) ---
// Kolom ini hanya diisi jika role_id merujuk ke 'Pelatih'
$table->string('no_sk')->nullable();
$table->date('tgl_sk')->nullable();
$table->date('masa_berlaku')->nullable();
$table->string('foto_sk')->nullable();
// --- LAINNYA ---
$table->text('catatan_medis')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('anggota');
}
};

View File

@ -0,0 +1,36 @@
<?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('jadwals', function (Blueprint $table) {
$table->id();
$table->foreignId('kolat_id')->constrained('kolat')->onDelete('cascade');
$table->foreignId('pelatih_id')->constrained('anggotas')->onDelete('cascade'); // Relasi ke anggota (yang rolenya pelatih)
$table->string('judul_kegiatan'); // Misal: Latihan Rutin, UKT, atau Pengumuman Libur
$table->enum('jenis', ['Rutin', 'Tambahan', 'Pengumuman']);
$table->date('tanggal');
$table->time('jam_mulai');
$table->time('jam_selesai')->nullable();
$table->string('lokasi');
$table->text('deskripsi')->nullable(); // Untuk isi pengumuman
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('jadwals');
}
};

View File

@ -0,0 +1,40 @@
<?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('presensis', function (Blueprint $table) {
$table->id();
$table->foreignId('jadwal_id')->constrained('jadwals')->onDelete('cascade');
$table->foreignId('anggota_id')->constrained('anggotas')->onDelete('cascade');
$table->enum('status', ['Hadir', 'Izin', 'Sakit', 'Alfa'])->default('Alfa');
$table->text('keterangan')->nullable();
$table->timestamp('waktu_presensi')->nullable();
// TAMBAHKAN 2 BARIS INI:
$table->boolean('is_verified')->default(false); // Untuk tahu sudah di-ACC pelatih atau belum
$table->unsignedBigInteger('verified_by')->nullable(); // Untuk catat ID pelatih yang meng-ACC
$table->timestamps();
// Tambahkan foreign key untuk verified_by
$table->foreign('verified_by')->references('id')->on('anggotas');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('presensis');
}
};

View File

@ -0,0 +1,40 @@
<?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('penilaians', function (Blueprint $table) {
$table->id();
$table->foreignId('pelatih_id')->constrained('anggotas')->onDelete('cascade');
$table->foreignId('anggota_id')->constrained('anggotas')->onDelete('cascade');
$table->date('bulan_evaluasi');
// 6 Kriteria Evaluasi Hasil Wawancara (Skala 1-5)
$table->integer('metode_pelatihan')->default(0);
$table->integer('komunikasi')->default(0);
$table->integer('sikap_kepribadian')->default(0);
$table->integer('kepemimpinan')->default(0);
$table->integer('konsistensi_komitmen')->default(0);
$table->integer('kedekatan_interpersonal')->default(0);
$table->text('kritik_saran')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('penilaians');
}
};

View File

@ -0,0 +1,29 @@
<?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::table('users', function (Blueprint $table) {
// $table->foreignId('role_id')->after('id')->constrained('role')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
// $table->dropForeign(['role_id']);
// $table->dropColumn('role_id');
});
}
};

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::table('anggotas', function (Blueprint $table) {
$table->foreignId('user_id')
->nullable()
->after('id')
->constrained('users')
->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('anggotas', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropColumn('user_id');
});
}
};

View File

@ -0,0 +1,28 @@
<?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::table('users', function (Blueprint $table) {
$table->dropColumn('name');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('name')->after('id')->nullable();
});
}
};

View File

@ -0,0 +1,30 @@
<?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::table('jadwals', function (Blueprint $table) {
// Tambahkan kolom status dengan default 'pending'
$table->string('status')->default('pending')->after('jenis');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('jadwals', function (Blueprint $table) {
$table->dropColumn('status');
});
}
};

View File

@ -0,0 +1,31 @@
<?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::table('jadwals', function (Blueprint $table) {
// Menambah kolom untuk simpan foto kegiatan (pengganti WA)
$table->string('foto_bukti')->nullable()->after('lokasi');
// Menambah kolom untuk materi/catatan latihan
$table->text('catatan_latihan')->nullable()->after('foto_bukti');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('jadwals', function (Blueprint $table) {
$table->dropColumn(['foto_bukti', 'catatan_latihan']);
});
}
};

View File

@ -0,0 +1,36 @@
<?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('kas', function (Blueprint $table) {
$table->id();
$table->date('tanggal');
$table->enum('jenis', ['masuk', 'keluar']);
// Kategori untuk membedakan asal uang (SPP, Seragam, Operasional, dll)
$table->string('kategori');
$table->decimal('nominal', 15, 2);
$table->text('keterangan')->nullable();
// Saldo akhir disimpan per baris transaksi agar history saldo terlihat jelas
$table->decimal('saldo_akhir', 15, 2)->default(0);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('kas');
}
};

View File

@ -0,0 +1,37 @@
<?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('spp', function (Blueprint $table) {
$table->id();
// Gunakan nama tabel sesuai yang ada di Schema::create tabel tujuan
$table->foreignId('anggota_id')->constrained('anggotas')->onDelete('cascade');
$table->foreignId('kolat_id')->constrained('kolat')->onDelete('cascade');
$table->foreignId('kas_id')->nullable()->constrained('kas')->onDelete('set null');
$table->string('bulan');
$table->year('tahun');
$table->date('jatuh_tempo');
$table->integer('nominal');
$table->enum('status', ['lunas', 'pending', 'belum_bayar']);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('spp');
}
};

View File

@ -0,0 +1,28 @@
<?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::table('spp', function (Blueprint $table) {
$table->string('bukti_pembayaran')->nullable()->after('status');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('spp', function (Blueprint $table) {
//
});
}
};

View File

@ -0,0 +1,28 @@
<?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::table('anggotas', function (Blueprint $table) {
$table->string('foto_profil')->nullable()->after('nama_lengkap');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('anggotas', function (Blueprint $table) {
$table->dropColumn('foto_profil');
});
}
};

View File

@ -0,0 +1,32 @@
<?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('kolat_pelatih', function (Blueprint $table) {
$table->id();
// Relasi ke anggota (yang bertindak sebagai pelatih) dan kolat
$table->foreignId('anggota_id')->constrained('anggotas')->onDelete('cascade');
$table->foreignId('kolat_id')->constrained('kolat')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('kolat_pelatih');
}
};

View File

@ -0,0 +1,37 @@
<?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('anggota_role', function (Blueprint $table) {
$table->id();
// Relasi ke tabel anggotas
$table->foreignId('anggota_id')->constrained('anggotas')->onDelete('cascade');
// Relasi ke tabel roles (asumsi nama tabel master role kamu adalah 'roles')
$table->foreignId('role_id')->constrained('role')->onDelete('cascade');
$table->timestamps();
// Kunci unik agar satu orang tidak bisa punya role yang sama persis secara ganda
$table->unique(['anggota_id', 'role_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('anggota_role');
}
};

View File

@ -0,0 +1,21 @@
<?php
namespace Database\Seeders;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
$this->call([
RoleSeeder::class,
KolatSeeder::class,
UserSeeder::class,
]);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class KolatSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Menghapus data lama agar tidak bentrok saat migrate:fresh --seed
DB::table('kolat')->delete();
DB::table('kolat')->insert([
[
'id' => 1, // Wajib ID 1 agar nyambung dengan UserSeeder (Siti, Budi, Andi)
'nama_kolat' => 'Kolat Unej',
'created_at' => now(),
'updated_at' => now(),
],
[
'id' => 2,
'nama_kolat' => 'Kolat Matasa',
'created_at' => now(),
'updated_at' => now(),
],
[
'id' => 3,
'nama_kolat' => 'Kolat Ajung',
'created_at' => now(),
'updated_at' => now(),
]
]);
}
}

Some files were not shown because too many files have changed in this diff Show More