This commit is contained in:
jouel88 2026-03-19 21:58:48 +07:00
parent cc98e0761e
commit e5cb2e916c
8 changed files with 192 additions and 113 deletions

71
README.md Normal file
View File

@ -0,0 +1,71 @@
# MPG HRIS - Web Management System
Sistem Manajemen Sumber Daya Manusia (HRIS) terintegrasi untuk pengelolaan data pegawai, presensi, lembur, dan perizinan. Dibuat menggunakan framework Laravel dengan fokus pada performa dan kemudahan penggunaan.
## 🚀 Tech Stack
- **Backend**: Laravel 12.x (PHP 8.2+)
- **Frontend**: Blade Templates + Tailwind CSS (v3.x)
- **Database**: MySQL 8.x
- **Icons**: Heroicons (SVG inline)
- **Komponen UI**: Custom Blade Components (Table, Modal, Input, Button, dll.)
## 🛠 Panduan Instalasi
Ikuti langkah-langkah berikut untuk menjalankan proyek di lingkungan lokal:
1. **Clone & Setup Environment**
```bash
cp .env.example .env
# Sesuaikan DB_DATABASE, DB_USERNAME, DB_PASSWORD di .env
```
2. **Instal Dependensi Backend**
```bash
composer install
```
3. **Instal Dependensi Frontend**
```bash
npm install
npm run build # atau npm run dev untuk development
```
4. **Generate App Key & Database**
```bash
php artisan key:generate
php artisan migrate --seed
```
5. **Akses Aplikasi**
Jalankan `php artisan serve` dan buka `http://localhost:8000`.
## 📂 Struktur Proyek & Arsitektur
Proyek ini mengikuti standar **Clean Code** Laravel dengan pemisahan tanggung jawab yang jelas:
- **Form Requests**: Semua validasi input diletakkan di `app/Http/Requests`.
- **Controllers**: Fokus pada pengaturan trafik. Logika query berat dipisahkan ke Eloquent Scopes atau Service.
- **Models**: Menggunakan relasi Eloquent yang jelas dan penamaan kolom `snake_case` Bahasa Indonesia.
- **Resources**: UI dibangun menggunakan komponen Blade di `resources/views/components`.
## 📜 Aturan Pengembangan (Project Rules)
1. **Bahasa**: Gunakan Bahasa Indonesia untuk penjelasan, komentar, dan dokumentasi.
2. **Naming Convention**:
- Primary Key: `id_nama_tabel` (contoh: `id_divisi`, `id_jabatan`).
- Tabel Master: `snake_case`.
3. **UI Components**: Gunakan komponen yang sudah ada untuk konsistensi:
- `<x-table>`: Wrapper tabel standar.
- `<x-input>` & `<x-select>`: Input form standar.
- `<x-button>` & `<x-back-button>`: Tombol aksi.
- `<x-modal>`: Untuk CRUD Data Master sederhana.
## 🔑 Akun Default (Seeder)
- **Super Admin**: `superadmin@mpg.co.id` / `password`
- **Admin**: `admin@mpg.co.id` / `admin123`
- **Pegawai**: `budi@mpg.co.id` / `Mpg123!`
---
Developed by MPG Team.

View File

@ -10,55 +10,38 @@ class DatabaseSeeder extends Seeder
{ {
public function run(): void public function run(): void
{ {
// Panggil MasterDataSeeder terlebih dahulu untuk data dasar (Divisi, Jabatan, Kantor, Role, User Admin)
DB::table('lokasi_kantor')->insert([
[
'nama_kantor' => 'Notis Digital',
'alamat' => 'Jln. Kalimantan No. 1, Jember',
'latitude' => -8.172110,
'longitude' => 113.700550,
'radius_meter' => 200,
'created_at' => now(),
'updated_at' => now(),
],
[
'nama_kantor' => 'Curah Manis Studio',
'alamat' => 'Jln. Mastrip, Jember',
'latitude' => -8.172110,
'longitude' => 113.700550,
'radius_meter' => 200,
'created_at' => now(),
'updated_at' => now(),
]
]);
User::factory(10)->create([
'id_kantor' => 1,
]);
User::factory(15)->create([
'id_kantor' => 2,
]);
User::factory()->create([
'nama_lengkap' => 'Julian (Super Admin)',
'email' => 'julian@admin.com',
'password' => bcrypt('password'),
'divisi' => 'IT',
'jabatan' => 'Manager',
'id_kantor' => 1,
]);
$this->call([ $this->call([
MasterDataSeeder::class, MasterDataSeeder::class,
]);
// Tambahan data demo jika diperlukan (Opsional)
$kantor = \App\Models\Kantor::first();
$divisi = \App\Models\Divisi::first();
$jabatan = \App\Models\Jabatan::where('nama_jabatan', 'Staff')->first() ?? \App\Models\Jabatan::first();
$users = User::factory(5)->create([
'id_kantor' => $kantor->id_kantor,
'id_divisi' => $divisi->id_divisi,
'id_jabatan' => $jabatan->id_jabatan,
]);
$rolePegawai = \App\Models\Role::where('nama_role', 'pegawai')->first();
if ($rolePegawai) {
foreach ($users as $user) {
$user->roles()->attach($rolePegawai->id_role);
}
}
// Panggil seeder fitur lainnya
$this->call([
PermissionSeeder::class, PermissionSeeder::class,
PresensiStatusSeeder::class, PresensiStatusSeeder::class,
PresensiSeeder::class, PresensiSeeder::class,
JenisIzinSeeder::class, JenisIzinSeeder::class,
StatusPengajuanSeeder::class, StatusPengajuanSeeder::class,
PengajuanIzinSeeder::class, PengajuanIzinSeeder::class,
SuperAdminSeeder::class, // Menambah super administrator dengan role admin
]); ]);
} }
} }

View File

@ -9,13 +9,12 @@ class JenisIzinSeeder extends Seeder
{ {
public function run(): void public function run(): void
{ {
DB::table('jenis_izin')->delete(); $types = ['Sakit', 'Cuti', 'Izin'];
foreach ($types as $type) {
DB::table('jenis_izin')->insert([ DB::table('jenis_izin')->updateOrInsert(
['nama_izin' => $type],
['id_jenis_izin' => 1, 'nama_izin' => 'Sakit'], ['updated_at' => now(), 'created_at' => now()]
['id_jenis_izin' => 2, 'nama_izin' => 'Cuti'], );
['id_jenis_izin' => 3, 'nama_izin' => 'Izin'], }
]);
} }
} }

View File

@ -13,54 +13,74 @@ class MasterDataSeeder extends Seeder
{ {
public function run() public function run()
{ {
$divIT = Divisi::firstOrCreate(['nama_divisi' => 'Information Technology']);
$divHR = Divisi::firstOrCreate(['nama_divisi' => 'Human Resources']);
Divisi::firstOrCreate(['nama_divisi' => 'Finance & Accounting']);
Divisi::firstOrCreate(['nama_divisi' => 'Marketing']);
$divIT = Divisi::create(['nama_divisi' => 'Information Technology']); $jabMgr = Jabatan::firstOrCreate(['nama_jabatan' => 'Manager']);
$divHR = Divisi::create(['nama_divisi' => 'Human Resources']); $jabSpv = Jabatan::firstOrCreate(['nama_jabatan' => 'Supervisor']);
Divisi::create(['nama_divisi' => 'Finance & Accounting']); $jabStf = Jabatan::firstOrCreate(['nama_jabatan' => 'Staff']);
Divisi::create(['nama_divisi' => 'Marketing']); Jabatan::firstOrCreate(['nama_jabatan' => 'Internship']);
$jabMgr = Jabatan::create(['nama_jabatan' => 'Manager']); $kPusat = Kantor::firstOrCreate(
$jabSpv = Jabatan::create(['nama_jabatan' => 'Supervisor']); ['nama_kantor' => 'Kantor Pusat'],
$jabStf = Jabatan::create(['nama_jabatan' => 'Staff']); [
Jabatan::create(['nama_jabatan' => 'Internship']); 'alamat' => 'Jl. Jendral Sudirman No. 1, Jakarta',
'latitude' => -6.2088,
'longitude' => 106.8456,
'radius' => 200
]
);
$kPusat = Kantor::create([ $kCabang = Kantor::firstOrCreate(
'nama_kantor' => 'Kantor Pusat', ['nama_kantor' => 'Curah Manis Studio'],
'alamat' => 'Jl. Jendral Sudirman No. 1, Jakarta', [
'tipe' => 'Pusat' 'alamat' => 'Jl. Curah Manis, Malang',
]); 'latitude' => -7.9839,
'longitude' => 112.6214,
'radius' => 200
]
);
$kCabang = Kantor::create([ // Roles
'nama_kantor' => 'Curah Manis Studio', $roleAdmin = \App\Models\Role::firstOrCreate(['nama_role' => 'admin']);
'alamat' => 'Jl. Curah Manis, Malang', $roleHrd = \App\Models\Role::firstOrCreate(['nama_role' => 'hrd']);
'tipe' => 'Cabang' $roleManager = \App\Models\Role::firstOrCreate(['nama_role' => 'manager']);
]); $rolePegawai = \App\Models\Role::firstOrCreate(['nama_role' => 'pegawai']);
User::create([ $admin = User::updateOrCreate(
'nama_lengkap' => 'Super Administrator', ['email' => 'admin@mpg.co.id'],
'email' => 'admin@mpg.co.id', [
'password' => Hash::make('admin123'), 'nama_lengkap' => 'Super Administrator',
'role' => 'admin', 'password' => Hash::make('admin123'),
'nik' => '999999999', 'nik' => '999999999',
'status_aktif' => 1, 'status_aktif' => 1,
'id_divisi' => $divIT->id_divisi,
'id_jabatan' => $jabMgr->id_jabatan,
'id_kantor' => $kPusat->id_kantor,
'tgl_bergabung' => now(),
]
);
if (!$admin->roles()->where('nama_role', 'admin')->exists()) {
$admin->roles()->attach($roleAdmin->id_role);
}
'id_divisi' => $divIT->id_divisi, $pegawai = User::updateOrCreate(
'id_jabatan' => $jabMgr->id_jabatan, ['email' => 'budi@mpg.co.id'],
'id_kantor' => $kPusat->id_kantor, [
'tgl_bergabung' => now(), 'nama_lengkap' => 'Budi Santoso',
]); 'password' => Hash::make('Mpg123!'),
'nik' => '350712345678',
User::create([ 'status_aktif' => 1,
'nama_lengkap' => 'Budi Santoso', 'id_divisi' => $divHR->id_divisi,
'email' => 'budi@mpg.co.id', 'id_jabatan' => $jabStf->id_jabatan,
'password' => Hash::make('Mpg123!'), 'id_kantor' => $kCabang->id_kantor,
'role' => 'pegawai', 'tgl_bergabung' => now(),
'nik' => '350712345678', ]
'status_aktif' => 1, );
'id_divisi' => $divHR->id_divisi, if (!$pegawai->roles()->where('nama_role', 'pegawai')->exists()) {
'id_jabatan' => $jabStf->id_jabatan, $pegawai->roles()->attach($rolePegawai->id_role);
'id_kantor' => $kCabang->id_kantor, }
'tgl_bergabung' => now(),
]);
} }
} }

View File

@ -21,10 +21,8 @@ public function run(): void
foreach ($users as $user) { foreach ($users as $user) {
try { try {
$idJenis = DB::table('jenis_izin')->inRandomOrder()->first()->id_jenis_izin;
$idJenis = rand(1, 3); $idStatus = DB::table('status_pengajuan')->inRandomOrder()->first()->id_status;
$idStatus = rand(1, 3);
$tglMulai = now()->addDays(rand(-10, 10)); $tglMulai = now()->addDays(rand(-10, 10));
$tglSelesai = (clone $tglMulai)->addDays(rand(1, 3)); $tglSelesai = (clone $tglMulai)->addDays(rand(1, 3));

View File

@ -40,12 +40,15 @@ public function run(): void
$randomMinutes = rand(-10, 30); $randomMinutes = rand(-10, 30);
$jamMasuk = $shiftStart->copy()->addMinutes($randomMinutes); $jamMasuk = $shiftStart->copy()->addMinutes($randomMinutes);
$status = 1; $idTepat = DB::table('status_presensi')->where('nama_status', 'Tepat Waktu')->value('id_status') ?? 1;
$idTelat = DB::table('status_presensi')->where('nama_status', 'Terlambat')->value('id_status') ?? 2;
$status = $idTepat;
$alasan = null; $alasan = null;
$toleransi = 10; $toleransi = 10;
if ($randomMinutes > $toleransi) { if ($randomMinutes > $toleransi) {
$status = 2; $status = $idTelat;
$alasan = 'Terlambat ' . ($randomMinutes) . ' menit (Dummy Data)'; $alasan = 'Terlambat ' . ($randomMinutes) . ' menit (Dummy Data)';
} }

View File

@ -10,14 +10,20 @@ class PresensiStatusSeeder extends Seeder
public function run(): void public function run(): void
{ {
$statuses = [
'Tepat Waktu',
'Terlambat',
'Izin',
'Sakit',
'Alpha',
];
DB::table('status_presensi')->insertOrIgnore([ foreach ($statuses as $status) {
['id_status' => 1, 'nama_status' => 'Tepat Waktu', 'created_at' => now(), 'updated_at' => now()], DB::table('status_presensi')->updateOrInsert(
['id_status' => 2, 'nama_status' => 'Terlambat', 'created_at' => now(), 'updated_at' => now()], ['nama_status' => $status],
['id_status' => 3, 'nama_status' => 'Izin', 'created_at' => now(), 'updated_at' => now()], ['created_at' => now(), 'updated_at' => now()]
['id_status' => 4, 'nama_status' => 'Sakit', 'created_at' => now(), 'updated_at' => now()], );
['id_status' => 5, 'nama_status' => 'Alpha', 'created_at' => now(), 'updated_at' => now()], }
]);
$this->command->info('Tabel status_presensi berhasil diisi!'); $this->command->info('Tabel status_presensi berhasil diisi!');
} }

View File

@ -9,13 +9,12 @@ class StatusPengajuanSeeder extends Seeder
{ {
public function run(): void public function run(): void
{ {
DB::table('status_pengajuan')->delete(); $statuses = ['Disetujui', 'Pending', 'Ditolak'];
foreach ($statuses as $status) {
DB::table('status_pengajuan')->insert([ DB::table('status_pengajuan')->updateOrInsert(
['nama_status' => $status],
['id_status' => 1, 'nama_status' => 'Disetujui'], ['updated_at' => now(), 'created_at' => now()]
['id_status' => 2, 'nama_status' => 'Pending'], );
['id_status' => 3, 'nama_status' => 'Ditolak'], }
]);
} }
} }