This commit is contained in:
jouel88 2026-03-20 03:07:13 +07:00
parent bffdb4bef3
commit d6c09fe1ae
7 changed files with 213 additions and 104 deletions

View File

@ -12,6 +12,7 @@
use Illuminate\Support\Facades\DB;
use App\Models\PenggunaanPoin;
use App\Services\PoinService;
use Illuminate\Support\Facades\Auth;
class JadwalController extends Controller
{

View File

@ -42,6 +42,45 @@ public function store(StoreTukarShiftRequest $request)
$jadwal1 = JadwalKerja::findOrFail($request->id_jadwal_1);
$jadwal2 = JadwalKerja::findOrFail($request->id_jadwal_2);
// 1. Validasi kepemilikan jadwal
if ($jadwal1->id_user != $request->id_user_1 || $jadwal2->id_user != $request->id_user_2) {
return redirect()->back()->withInput()->with('error', 'Data jadwal tidak sesuai dengan pegawai yang dipilih.');
}
// 2. Validasi bentrok jadwal kerja
$conflict1 = JadwalKerja::where('id_user', $request->id_user_1)
->where('tanggal', $jadwal2->tanggal)
->where('id_jadwal', '!=', $jadwal1->id_jadwal)
->exists();
if ($conflict1) {
return redirect()->back()->withInput()->with('error', 'Pegawai Pertama sudah memiliki jadwal kerja lain pada tanggal tujuan (' . $jadwal2->tanggal . ').');
}
$conflict2 = JadwalKerja::where('id_user', $request->id_user_2)
->where('tanggal', $jadwal1->tanggal)
->where('id_jadwal', '!=', $jadwal2->id_jadwal)
->exists();
if ($conflict2) {
return redirect()->back()->withInput()->with('error', 'Pegawai Kedua sudah memiliki jadwal kerja lain pada tanggal tujuan (' . $jadwal1->tanggal . ').');
}
// 3. Validasi bentrok dengan Penggunaan Poin (Cuti / dll)
$poin1 = \App\Models\PenggunaanPoin::where('id_user', $request->id_user_1)
->where('tanggal_penggunaan', $jadwal2->tanggal)
->where('id_status', 2)
->exists();
if ($poin1) {
return redirect()->back()->withInput()->with('error', 'Pegawai Pertama memiliki riwayat Cuti/Penggunaan Poin pada tanggal tujuan (' . $jadwal2->tanggal . ').');
}
$poin2 = \App\Models\PenggunaanPoin::where('id_user', $request->id_user_2)
->where('tanggal_penggunaan', $jadwal1->tanggal)
->where('id_status', 2)
->exists();
if ($poin2) {
return redirect()->back()->withInput()->with('error', 'Pegawai Kedua memiliki riwayat Cuti/Penggunaan Poin pada tanggal tujuan (' . $jadwal1->tanggal . ').');
}
$tempUser1 = $jadwal1->id_user;
$jadwal1->id_user = $jadwal2->id_user;
$jadwal2->id_user = $tempUser1;

View File

@ -108,6 +108,20 @@ public function hasPermission($permissionSlug): bool
})->exists();
}
public function isGlobalAdmin(): bool
{
return $this->roles->contains(function ($role) {
return in_array(strtolower($role->nama_role), ['admin', 'super admin', 'super_admin']);
});
}
public function isSuperAdmin(): bool
{
return $this->roles->contains(function ($role) {
return in_array(strtolower($role->nama_role), ['super admin', 'super_admin']);
});
}
public function kantor(): BelongsTo
{
return $this->belongsTo(Kantor::class, 'id_kantor', 'id_kantor');

View File

@ -1,4 +1,4 @@
<aside
<aside
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'"
class="fixed inset-y-0 left-0 w-72 bg-white border-r border-slate-200 flex flex-col h-full transition-transform duration-300 font-sans z-[100] shadow-xl lg:shadow-sm lg:static lg:flex-shrink-0">
@ -15,6 +15,8 @@ class="fixed inset-y-0 left-0 w-72 bg-white border-r border-slate-200 flex flex-
$isAdmin = Auth::user()->roles->contains(function ($role) {
return strtolower($role->nama_role) === 'admin';
});
$isSuperAdmin = Auth::user()->isSuperAdmin();
$isAdminOrSuperAdmin = Auth::user()->isGlobalAdmin();
@endphp
<nav class="flex-1 px-4 py-6 space-y-2 overflow-y-auto no-scrollbar">
@ -31,7 +33,7 @@ class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group
</a>
</div>
@if($isAdmin)
@if($isAdminOrSuperAdmin)
<div class="pt-6 pb-3 px-4 flex items-center gap-3">
<div class="h-px bg-slate-200 flex-1"></div>
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Manajemen</span>
@ -39,6 +41,7 @@ class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group
</div>
<div class="space-y-1">
@if($isSuperAdmin)
<a href="{{ route('face.index') }}"
class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group {{ request()->routeIs('face.*') ? 'bg-[#130F26] text-white shadow-lg shadow-[#130F26]/30' : 'text-slate-500 hover:bg-slate-50 hover:text-slate-900' }}">
<svg class="w-5 h-5 mr-3 {{ request()->routeIs('face.*') ? 'text-white' : 'text-slate-400 group-hover:text-slate-600' }} group-hover:scale-110 transition-transform duration-300"
@ -53,6 +56,7 @@ class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group
class="ml-auto bg-red-500 text-white text-[10px] font-bold px-2 py-0.5 rounded-full">{{ $pendingFace }}</span>
@endif
</a>
@endif
<a href="{{ route('pegawai.index') }}"
class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group {{ request()->routeIs('pegawai.*') ? 'bg-[#130F26] text-white shadow-lg shadow-[#130F26]/30' : 'text-slate-500 hover:bg-slate-50 hover:text-slate-900' }}">
@ -77,7 +81,7 @@ class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group
</div>
@endif
@if($isAdmin)
@if($isAdminOrSuperAdmin)
<div class="pt-6 pb-3 px-4 flex items-center gap-3">
<div class="h-px bg-slate-200 flex-1"></div>
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Absensi</span>
@ -95,7 +99,7 @@ class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group
<span class="font-semibold text-sm">Laporan Kehadiran</span>
</a>
@if($isAdmin)
@if($isAdminOrSuperAdmin)
<a href="{{ route('laporan-lembur.index') }}"
class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group {{ request()->routeIs('laporan-lembur.*') ? 'bg-[#130F26] text-white shadow-lg shadow-[#130F26]/30' : 'text-slate-500 hover:bg-slate-50 hover:text-slate-900' }}">
<svg class="w-5 h-5 mr-3 {{ request()->routeIs('laporan-lembur.*') ? 'text-white' : 'text-slate-400 group-hover:text-slate-600' }} group-hover:scale-110 transition-transform duration-300"
@ -246,10 +250,12 @@ class="block py-2.5 px-4 text-sm rounded-lg transition-all {{ request()->routeIs
Data Jabatan
</a>
@if($isSuperAdmin)
<a href="{{ route('role.index') }}"
class="block py-2.5 px-4 text-sm rounded-lg transition-all {{ request()->routeIs('role.*') ? 'bg-slate-100 text-[#130F26] font-bold border-l-4 border-[#130F26]' : 'text-slate-500 hover:text-slate-900 hover:bg-slate-50' }}">
Data Role
</a>
@endif
<a href="{{ route('shift.index') }}"
class="block py-2.5 px-4 text-sm rounded-lg transition-all {{ request()->routeIs('shift.*') ? 'bg-slate-100 text-[#130F26] font-bold border-l-4 border-[#130F26]' : 'text-slate-500 hover:text-slate-900 hover:bg-slate-50' }}">

View File

@ -1,4 +1,4 @@
@extends('layouts.app')
@extends('layouts.app')
@section('title', 'Tambah Pegawai')
@ -17,79 +17,104 @@
class="grid grid-cols-1 lg:grid-cols-3 gap-6">
@csrf
<div class="lg:col-span-2 space-y-6">
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
<h3 class="text-lg font-bold text-slate-800 mb-4">Informasi Pribadi</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-200 block">
<h3 class="text-lg font-bold text-slate-800 mb-4 block w-full">Informasi Pribadi</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 w-full">
<x-input label="Nomor Induk Kewarganegaraan (NIK)" name="nik" placeholder="Masukkan NIK sesuai KTP"
required />
<div class="w-full">
<x-input label="Nomor Induk Kewarganegaraan (NIK)" name="nik"
placeholder="Masukkan NIK sesuai KTP" required />
</div>
<div class="w-full">
<x-input label="Nama Lengkap" name="nama_lengkap" placeholder="Masukkan nama sesuai KTP" required />
<x-input label="Email" name="email" type="email" placeholder="contoh@perusahaan.com" required />
</div>
<div class="w-full">
<x-input label="Email" name="email" type="email" placeholder="contoh@perusahaan.com"
required />
</div>
<div class="w-full">
<x-input label="Nomor Telepon" name="no_telp" placeholder="0812..." />
</div>
<div class="w-full">
<x-input label="Sisa Cuti (Hari)" name="sisa_cuti" type="number" value="12"
placeholder="Default: 12" required />
</div>
<div class="mt-4">
<x-textarea label="Alamat Lengkap" name="alamat" rows="3" placeholder="Alamat domisili saat ini" />
<div class="md:col-span-2 w-full mt-2">
<x-textarea label="Alamat Lengkap" name="alamat" rows="3"
placeholder="Alamat domisili saat ini" />
</div>
</div>
</div>
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
<h3 class="text-lg font-bold text-slate-800 mb-4">Penempatan Kerja</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="w-full">
<x-select label="Divisi" name="id_divisi" required>
<option value="">Pilih Divisi</option>
@foreach($divisi as $d)
@foreach ($divisi as $d)
<option value="{{ $d->id_divisi }}">{{ $d->nama_divisi }}</option>
@endforeach
</x-select>
</div>
<div class="w-full">
<x-select label="Jabatan" name="id_jabatan" required>
<option value="">Pilih Jabatan</option>
@foreach($jabatan as $j)
@foreach ($jabatan as $j)
<option value="{{ $j->id_jabatan }}">{{ $j->nama_jabatan }}</option>
@endforeach
</x-select>
</div>
<div class="w-full">
<x-select label="Lokasi Kantor" name="id_kantor" required>
<option value="">Pilih Kantor</option>
@foreach($kantor as $k)
@foreach ($kantor as $k)
<option value="{{ $k->id_kantor }}">{{ $k->nama_kantor }}</option>
@endforeach
</x-select>
</div>
<div class="w-full">
<x-select label="Role Akses" name="id_role" required>
<option value="">Pilih Role Akses</option>
@foreach($roles as $r)
@foreach ($roles as $r)
<option value="{{ $r->id_role }}">{{ $r->nama_role }}</option>
@endforeach
</x-select>
</div>
<div class="md:col-span-2 w-full mt-2">
<x-date-input label="Tanggal Bergabung" name="tgl_bergabung" required />
</div>
</div>
</div>
</div>
<div class="lg:col-span-1 space-y-6">
<div class="lg:col-span-1">
<div class="space-y-4 sticky top-6">
<x-image-upload label="Foto Profil" name="foto" id="foto" />
<div class="flex flex-col gap-3">
<div class="space-y-2 mt-2">
<x-button type="submit" id="submitBtn"
class="w-full justify-center py-3 bg-[#130F26] hover:bg-slate-800">
<span id="submitText">Simpan Data Pegawai</span>
</x-button>
<x-button href="{{ route('pegawai.index') }}" variant="white" class="w-full justify-center py-3">
<x-button href="{{ route('pegawai.index') }}" variant="secondary" class="w-full justify-center py-3">
Batalkan
</x-button>
</div>
<div class="bg-amber-50 border border-amber-200 p-4 rounded-xl">
<div class="bg-amber-50 border border-amber-200 p-4 rounded-xl mt-2">
<p class="text-xs text-amber-800 leading-relaxed">
<strong>Informasi:</strong> Kata sandi default untuk pegawai baru adalah <code
class="bg-amber-200 px-1 rounded">Mpg123!</code>.
</p>
</div>
</div>
</div>
</form>
</div>
@endsection

View File

@ -1,4 +1,4 @@
@extends('layouts.app')
@extends('layouts.app')
@section('title', 'Edit Pegawai')
@ -18,41 +18,57 @@ class="grid grid-cols-1 lg:grid-cols-3 gap-6">
@method('PUT')
<div class="lg:col-span-2 space-y-6">
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
<h3 class="text-lg font-bold text-slate-800 mb-4">Informasi Pribadi</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<x-input label="Nomor Induk Kewarganegaraan (NIK)" name="nik" value="{{ $pegawai->nik }}" readonly
class="bg-slate-50 opacity-70" />
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-200 block">
<h3 class="text-lg font-bold text-slate-800 mb-4 block w-full">Informasi Pribadi</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 w-full">
<div class="w-full">
<x-input label="Nomor Induk Kewarganegaraan (NIK)" name="nik" value="{{ $pegawai->nik }}"
readonly class="bg-slate-50 opacity-70" />
</div>
<div class="w-full">
<x-input label="Nama Lengkap" name="nama_lengkap" value="{{ $pegawai->nama_lengkap }}" required />
</div>
<div class="w-full">
<x-input label="Email" name="email" type="email" value="{{ $pegawai->email }}" required />
</div>
<div class="w-full">
<x-input label="Nomor Telepon" name="no_telp" value="{{ $pegawai->no_telp }}" />
</div>
<div class="w-full">
<x-input label="Sisa Cuti (Hari)" name="sisa_cuti" type="number" value="{{ $pegawai->sisa_cuti }}"
required />
</div>
<div class="mt-4">
<x-textarea label="Alamat Lengkap" name="alamat" value="{{ $pegawai->alamat }}" rows="3" />
<div class="md:col-span-2 w-full mt-2">
<x-textarea label="Alamat Lengkap" name="alamat" value="{{ $pegawai->alamat }}"
rows="3" />
</div>
</div>
</div>
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
<h3 class="text-lg font-bold text-slate-800 mb-4">Penempatan & Status</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-200 block">
<h3 class="text-lg font-bold text-slate-800 mb-4 block w-full">Penempatan & Status</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 w-full">
<x-select label="Divisi" name="id_divisi" required>
@foreach($divisi as $d)
<option value="{{ $d->id_divisi }}" {{ $pegawai->id_divisi == $d->id_divisi ? 'selected' : '' }}>
@foreach ($divisi as $d)
<option value="{{ $d->id_divisi }}"
{{ $pegawai->id_divisi == $d->id_divisi ? 'selected' : '' }}>
{{ $d->nama_divisi }}</option>
@endforeach
</x-select>
<x-select label="Jabatan" name="id_jabatan" required>
@foreach($jabatan as $j)
<option value="{{ $j->id_jabatan }}" {{ $pegawai->id_jabatan == $j->id_jabatan ? 'selected' : '' }}>{{ $j->nama_jabatan }}</option>
@foreach ($jabatan as $j)
<option value="{{ $j->id_jabatan }}"
{{ $pegawai->id_jabatan == $j->id_jabatan ? 'selected' : '' }}>{{ $j->nama_jabatan }}
</option>
@endforeach
</x-select>
<x-select label="Lokasi Kantor" name="id_kantor" required>
@foreach($kantor as $k)
<option value="{{ $k->id_kantor }}" {{ $pegawai->id_kantor == $k->id_kantor ? 'selected' : '' }}>
@foreach ($kantor as $k)
<option value="{{ $k->id_kantor }}"
{{ $pegawai->id_kantor == $k->id_kantor ? 'selected' : '' }}>
{{ $k->nama_kantor }}</option>
@endforeach
</x-select>
@ -63,7 +79,7 @@ class="bg-slate-50 opacity-70" />
</x-select>
<x-select label="Role Akses" name="id_role" required>
@foreach($roles as $r)
@foreach ($roles as $r)
<option value="{{ $r->id_role }}"
{{ $pegawai->roles->contains('id_role', $r->id_role) ? 'selected' : '' }}>
{{ $r->nama_role }}
@ -79,20 +95,22 @@ class="bg-slate-50 opacity-70" />
</div>
</div>
<div class="lg:col-span-1 space-y-6">
<div class="lg:col-span-1">
<div class="space-y-4 sticky top-6">
<x-image-upload label="Foto Profil" name="foto" id="foto"
default="{{ $pegawai->foto ? asset('storage/' . $pegawai->foto) : '' }}" />
<div class="flex flex-col gap-3">
<div class="space-y-2 mt-2">
<x-button type="submit" id="submitBtn"
class="w-full justify-center py-3 bg-amber-500 hover:bg-amber-600 shadow-amber-200">
<span id="submitText">Perbarui Data Pegawai</span>
</x-button>
<x-button href="{{ route('pegawai.index') }}" variant="white" class="w-full justify-center py-3">
<x-button href="{{ route('pegawai.index') }}" variant="secondary" class="w-full justify-center py-3">
Batalkan
</x-button>
</div>
</div>
</div>
</form>
</div>
@endsection

View File

@ -37,9 +37,6 @@
Route::post('/izin/{id}/approve', [App\Http\Controllers\PengajuanIzinController::class, 'approve'])->name('izin.approve');
Route::post('/izin/{id}/reject', [App\Http\Controllers\PengajuanIzinController::class, 'reject'])->name('izin.reject');
Route::get('/permissions', [PermissionController::class, 'index'])->name('permission.index');
Route::post('/permissions', [PermissionController::class, 'store'])->name('permission.store');
Route::post('/permissions/sync/{id_role}', [PermissionController::class, 'sync'])->name('permission.sync');
Route::resource('pengumuman', PengumumanController::class);
@ -53,10 +50,6 @@
Route::get('/laporan/export', [LaporanController::class, 'exportExcel'])->name('laporan.export');
Route::get('/laporan/export-pdf', [LaporanController::class, 'exportPdf'])->name('laporan.exportPdf');
Route::get('/face-approval', [FaceApprovalController::class, 'index'])->name('face.index');
Route::get('/face-approval/photo/{userId}/{pose}', [FaceApprovalController::class, 'showPhoto'])->name('face.photo');
Route::put('/face-approval/{id}/approve', [FaceApprovalController::class, 'approve'])->name('face.approve');
Route::delete('/face-approval/{id}/reject', [FaceApprovalController::class, 'reject'])->name('face.reject');
Route::prefix('jadwal')->name('jadwal.')->group(function () {
Route::get('/generate', [App\Http\Controllers\JadwalController::class, 'generateForm'])->name('generate');
@ -85,7 +78,7 @@
Route::resource('divisi', DivisiController::class)->except(['create', 'edit', 'show']);
Route::resource('jabatan', JabatanController::class);
Route::resource('kantor', KantorController::class)->except(['create', 'show', 'edit']);
Route::resource('role', RoleController::class)->except(['create', 'edit']);
// Role route moved to super admin group
Route::resource('shift', \App\Http\Controllers\ShiftController::class)->except(['create', 'edit', 'show']);
Route::resource('jadwal', \App\Http\Controllers\JadwalController::class)->except(['edit', 'show']);
Route::resource('pegawai', PegawaiController::class);
@ -107,3 +100,16 @@
Route::get('/tukar-shift/jadwal-user', [\App\Http\Controllers\TukarShiftController::class, 'getJadwalUser'])->name('tukar-shift.jadwal-user');
Route::resource('tukar-shift', \App\Http\Controllers\TukarShiftController::class)->only(['index', 'create', 'store']);
});
Route::middleware(['auth', 'role:super admin,super_admin'])->group(function () {
Route::get('/permissions', [PermissionController::class, 'index'])->name('permission.index');
Route::post('/permissions', [PermissionController::class, 'store'])->name('permission.store');
Route::post('/permissions/sync/{id_role}', [PermissionController::class, 'sync'])->name('permission.sync');
Route::get('/face-approval', [FaceApprovalController::class, 'index'])->name('face.index');
Route::get('/face-approval/photo/{userId}/{pose}', [FaceApprovalController::class, 'showPhoto'])->name('face.photo');
Route::put('/face-approval/{id}/approve', [FaceApprovalController::class, 'approve'])->name('face.approve');
Route::delete('/face-approval/{id}/reject', [FaceApprovalController::class, 'reject'])->name('face.reject');
Route::resource('role', RoleController::class)->except(['create', 'edit']);
});