whereNotNull('department_id') ->with('department') ->groupBy('department_id') ->get(); $chartPos = User::select('position_id', DB::raw('count(*) as total')) ->whereNotNull('position_id') ->with('position') ->groupBy('position_id') ->get(); $departments = Department::with('positions')->get(); $positions = Position::all(); $deptPosMapping = $departments->mapWithKeys(function ($dept) { return [$dept->id => $dept->positions->map(function($pos) { return ['id' => $pos->id, 'name' => $pos->name]; })]; }); $query = User::with(['department', 'position', 'roles']); if ($request->filled('search')) { $search = $request->search; $columns = \Illuminate\Support\Facades\Schema::getColumnListing('users'); $query->where(function($q) use ($search, $columns) { foreach ($columns as $column) { $q->orWhere($column, 'like', "%{$search}%"); } }); } if ($request->filled('department_id')) { $query->where('department_id', $request->department_id); } if ($request->filled('position_id')) { $query->where('position_id', $request->position_id); } $employees = $query->latest()->paginate(10)->withQueryString(); $totalRecords = $employees->total(); return view('pages.admin.employee.index', compact( 'employees', 'totalRecords', 'departments', 'positions', 'chartDept', 'chartPos', 'deptPosMapping' )); } public function create() { $departments = Department::with('positions')->orderBy('name', 'asc')->get(); $positions = Position::all(); $deptPosMapping = $departments->mapWithKeys(function ($dept) { return [$dept->id => $dept->positions->map(function($pos) { return ['id' => $pos->id, 'name' => $pos->name]; })]; }); $marketingDeptIds = Department::where('name', 'LIKE', '%MKT%') ->orWhere('name', 'LIKE', '%MARKETING%') ->pluck('id'); $trainingMatrices = TrainingMatrix::whereIn('department_id', $marketingDeptIds)->get(); $existingInitials = User::whereNotNull('initial') ->pluck('initial') ->map(fn($i) => strtolower($i)) ->toArray(); return view('pages.admin.employee.create', compact( 'departments', 'positions', 'trainingMatrices', 'deptPosMapping', 'existingInitials' )); } public function store(Request $request) { $validated = $request->validate([ 'nik' => 'required|unique:users,nik', 'identity_number' => 'nullable|string|max:50', 'initial' => 'nullable|string|max:10', 'first_name' => 'required|string|max:255', 'last_name' => 'nullable|string|max:255', 'phone' => 'nullable|string|max:20', 'email' => 'required|email|unique:users,email', 'role' => 'nullable|string', 'is_trainer' => 'nullable|boolean', 'gender' => 'required|in:L,P', 'date_of_birth' => 'nullable|date', 'join_date' => 'nullable|date', 'department_id' => 'required|exists:departments,id', 'position_id' => 'required|exists:positions,id', 'training_matrix_id' => 'nullable|integer', 'documents.*' => 'nullable|file|max:5120', ]); DB::beginTransaction(); try { $validated['password'] = Hash::make('Karyawan123!'); $validated['role'] = $request->role ?? 'karyawan'; $validated['must_change_password'] = true; $userData = Arr::except($validated, ['documents', 'is_trainer']); $user = User::create($userData); $rolesToAssign = []; if ($validated['role'] == 'admin') { $rolesToAssign[] = 'admin'; } elseif ($validated['role'] == 'trainer') { $rolesToAssign[] = 'trainer'; $rolesToAssign[] = 'trainee'; } else { $rolesToAssign[] = 'trainee'; } if ($request->has('is_trainer') && $request->is_trainer) { $rolesToAssign[] = 'trainer'; $rolesToAssign[] = 'trainee'; } $user->assignRole(array_unique($rolesToAssign)); if ($request->hasFile('documents')) { foreach ($request->file('documents') as $file) { $fileName = time() . '_' . str_replace(' ', '_', $file->getClientOriginalName()); $path = $file->storeAs('employee_docs/' . $user->nik, $fileName, 'nextcloud'); DB::table('employee_documents')->insert([ 'user_id' => $user->id, 'file_name' => $file->getClientOriginalName(), 'file_path' => $path, 'created_at' => now(), 'updated_at' => now(), ]); } } DB::table('audit_trails')->insert([ 'user_id' => auth()->id(), 'action' => 'Create Employee', 'description' => "Menambahkan karyawan baru: {$user->first_name} {$user->last_name} (NIK: {$user->nik})", 'ip_address' => $request->ip(), 'created_at' => now(), ]); DB::commit(); // FIX: Menggunakan rute jamak (employees) return redirect()->route('admin.employees.index') ->with('success', "Data Karyawan {$user->first_name} berhasil ditambahkan! Password default: Karyawan123!"); } catch (\Exception $e) { DB::rollBack(); return back()->with('error', 'Gagal menyimpan data! ' . $e->getMessage())->withInput(); } } public function show(User $employee) { $employee->load(['department', 'position', 'roles']); return view('pages.admin.employee.show', compact('employee')); } public function edit(User $employee) { $departments = Department::with('positions')->orderBy('name', 'asc')->get(); $positions = Position::all(); $deptPosMapping = $departments->mapWithKeys(function ($dept) { return [$dept->id => $dept->positions->map(function($pos) { return ['id' => $pos->id, 'name' => $pos->name]; })]; }); $marketingDeptIds = Department::where('name', 'LIKE', '%MKT%')->orWhere('name', 'LIKE', '%MARKETING%')->pluck('id'); $trainingMatrices = TrainingMatrix::whereIn('department_id', $marketingDeptIds)->get(); return view('pages.admin.employee.edit', compact('employee', 'departments', 'positions', 'trainingMatrices', 'deptPosMapping')); } public function update(Request $request, User $employee) { $validated = $request->validate([ 'nik' => ['required', Rule::unique('users', 'nik')->ignore($employee->id)], 'identity_number' => 'nullable|string|max:50', 'initial' => 'nullable|string|max:10', 'first_name' => 'required|string|max:255', 'last_name' => 'nullable|string|max:255', 'phone' => 'nullable|string|max:20', 'email' => ['required', 'email', Rule::unique('users', 'email')->ignore($employee->id)], 'role' => 'nullable|string', 'is_trainer' => 'nullable|boolean', 'gender' => 'required|in:L,P', 'date_of_birth' => 'nullable|date', 'join_date' => 'nullable|date', 'department_id' => 'required|exists:departments,id', 'position_id' => 'required|exists:positions,id', 'training_matrix_id' => 'nullable|integer', 'is_active' => 'nullable|boolean', ]); DB::beginTransaction(); try { if ($request->filled('password')) { $validated['password'] = Hash::make($request->password); } $userData = Arr::except($validated, ['is_trainer', 'is_active']); $userData['is_active'] = $request->has('is_active'); $employee->update($userData); $rolesToAssign = []; $inputRole = $validated['role'] ?? 'karyawan'; if ($inputRole == 'admin') { $rolesToAssign[] = 'admin'; } elseif ($inputRole == 'trainer' || $request->is_trainer) { $rolesToAssign[] = 'trainer'; $rolesToAssign[] = 'trainee'; } else { $rolesToAssign[] = 'trainee'; } $employee->syncRoles(array_unique($rolesToAssign)); DB::table('audit_trails')->insert([ 'user_id' => auth()->id(), 'action' => 'Update Employee', 'description' => "Memperbarui data karyawan: {$employee->first_name} (NIK: {$employee->nik})", 'ip_address' => $request->ip(), 'created_at' => now(), ]); DB::commit(); // FIX: Menggunakan rute jamak (employees) return redirect()->route('admin.employees.index')->with('success', "Data Karyawan {$employee->first_name} berhasil diperbarui."); } catch (\Exception $e) { DB::rollBack(); return back()->with('error', 'Gagal memperbarui data! ' . $e->getMessage())->withInput(); } } public function destroy(User $employee) { if ($employee->hasRole('admin')) { return back()->with('error', 'Aksi ditolak! Anda tidak dapat menonaktifkan akun Administrator.'); } $nama = $employee->first_name; $employee->update(['is_active' => false]); DB::table('audit_trails')->insert([ 'user_id' => auth()->id(), 'action' => 'Deactivate Employee', 'description' => "Menonaktifkan karyawan (Resign/Keluar): {$nama}", 'ip_address' => request()->ip(), 'created_at' => now(), ]); // FIX: Menggunakan rute jamak (employees) return redirect()->route('admin.employees.index')->with('success', "Karyawan {$nama} berhasil dinonaktifkan. Data historis tetap tersimpan."); } // ========================================================================= // FITUR EXPORT & IMPORT // ========================================================================= private function getFilteredEmployees(Request $request) { $query = User::with(['department', 'position', 'roles'])->whereNotNull('department_id'); if ($request->filled('search')) { $search = $request->search; $columns = \Illuminate\Support\Facades\Schema::getColumnListing('users'); $query->where(function($q) use ($search, $columns) { foreach ($columns as $column) { $q->orWhere($column, 'like', "%{$search}%"); } }); } if ($request->filled('department_id')) { $query->where('department_id', $request->department_id); } if ($request->filled('position_id')) { $query->where('position_id', $request->position_id); } return $query->latest()->get(); // Ambil semua data TANPA pagination } public function exportExcel(Request $request) { // Ambil data yang sudah difilter $employees = $this->getFilteredEmployees($request); $fileName = 'Data_Karyawan_' . date('Y-m-d_H-i') . '.xlsx'; // Lempar data ke Class Export return \Maatwebsite\Excel\Facades\Excel::download(new \App\Exports\EmployeesExport($employees), $fileName); } public function exportPdf(Request $request) { // Ambil data yang sudah difilter $employees = $this->getFilteredEmployees($request); // PENTING: Perhatikan penamaan folder (employee tanpa 's') $pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('pages.admin.employee.pdf', compact('employees')) ->setPaper('a4', 'landscape'); return $pdf->download('Data_Karyawan_' . date('Y-m-d_H-i') . '.pdf'); } public function importView() { return view('pages.admin.employee.import'); } public function downloadTemplate() { // Menggunakan library Excel untuk menghasilkan format murni .xlsx return \Maatwebsite\Excel\Facades\Excel::download(new \App\Exports\EmployeeTemplateExport, 'Template_Import_Karyawan.xlsx'); } public function importPreview(Request $request) { $request->validate([ 'file' => 'required|mimes:xlsx,xls,csv|max:5120' ]); try { $rows = \Maatwebsite\Excel\Facades\Excel::toArray(new class implements \Maatwebsite\Excel\Concerns\ToModel, \Maatwebsite\Excel\Concerns\WithHeadingRow { public function model(array $row) { return $row; } }, $request->file('file'))[0]; session()->put('import_rows_data', $rows); return view('pages.admin.employee.import-preview', compact('rows')); } catch (\Exception $e) { return back()->with('error', 'Gagal membaca format file Excel. Pastikan menggunakan template yang benar. Error: ' . $e->getMessage()); } } public function importProcess(Request $request) { $rows = session('import_rows_data'); if (!$rows) { return redirect()->route('admin.employees.import')->with('error', 'Sesi import telah kedaluwarsa atau kosong. Silakan unggah ulang file Anda.'); } $successCount = 0; $failCount = 0; foreach ($rows as $row) { try { if (empty($row['nik']) || empty($row['nama_depan']) || empty($row['email'])) { $failCount++; continue; } $user = User::updateOrCreate( ['nik' => $row['nik']], // Jika NIK sama, perbarui. Jika tidak ada, buat baru. [ 'identity_number' => $row['no_ktp'] ?? null, 'initial' => !empty($row['inisial']) ? strtoupper($row['inisial']) : null, 'first_name' => $row['nama_depan'], 'last_name' => $row['nama_belakang'] ?? null, 'email' => $row['email'], 'phone' => $row['telepon'] ?? null, 'gender' => in_array(strtoupper($row['jenis_kelamin'] ?? ''), ['L', 'P']) ? strtoupper($row['jenis_kelamin']) : 'L', 'date_of_birth' => !empty($row['tgl_lahir']) ? \Carbon\Carbon::parse($row['tgl_lahir'])->format('Y-m-d') : null, 'join_date' => !empty($row['tgl_masuk']) ? \Carbon\Carbon::parse($row['tgl_masuk'])->format('Y-m-d') : null, 'department_id' => $row['department_id'] ?? 1, 'position_id' => $row['position_id'] ?? 1, 'password' => Hash::make('12345678'), 'is_active' => true, 'must_change_password' => true, ] ); if (!$user->hasAnyRole(['admin', 'trainer', 'trainee'])) { $user->assignRole('trainee'); } $successCount++; } catch (\Exception $e) { $failCount++; } } session()->forget('import_rows_data'); return redirect()->route('admin.employees.index') ->with('success', "Proses Import Selesai! Berhasil: {$successCount} data. Gagal/Dilewati: {$failCount} data."); } }