where('is_archived', false); if ($user && $user->role === 'user') { $query->whereHas('petugas', function($q) use ($user) { $q->where('users.id', $user->id); }); } $totalTugas = (clone $query)->where('status', '!=', 'dibatalkan')->count(); $proses = (clone $query)->where('status', 'proses')->count(); $selesai = (clone $query)->where('status', 'selesai')->count(); $telahDitinjau = (clone $query)->where('status', 'telah_ditinjau')->count(); $dibatalkan = (clone $query)->where('status', 'dibatalkan')->count(); // Ambil 5 tugas terbaru beserta data petugasnya, kecuali yang dibatalkan $terbaru = (clone $query)->with('petugas')->where('status', '!=', 'dibatalkan')->latest()->limit(5)->get(); return response()->json([ 'success' => true, 'data' => [ 'stats' => [ 'total_tugas' => $totalTugas, 'status_proses' => $proses, 'status_selesai' => $selesai, 'status_ditinjau' => $telahDitinjau, 'status_batal' => $dibatalkan, ], 'recent_activities' => $terbaru ] ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()], 500); } } /** * 2. LIST SEMUA TUGAS */ public function index(Request $request) { $user = Auth::user(); $query = Penugasan::with(['petugas', 'pembuat', 'progress.user']); if ($user && $user->role === 'user') { $query->whereHas('petugas', function($q) use ($user) { $q->where('users.id', $user->id); }); } if ($request->has('is_archived') && $request->is_archived == 'true') { $query->where('is_archived', true); } else { $query->where('is_archived', false); } $data = $query->latest()->get(); return response()->json(['success' => true, 'data' => $data]); } /** * 3. SIMPAN TUGAS BARU (SINKRON DENGAN FRONTEND) */ public function store(Request $request) { $request->validate([ 'nomor_surat' => 'nullable|string|unique:penugasan,nomor_surat', 'kegiatan' => 'required|string', 'tanggal_mulai' => 'required|date', 'pegawai_ids' => 'required|array|min:1', ], [ 'nomor_surat.unique' => 'Nomor surat ini sudah terdaftar di sistem (termasuk yang sudah dihapus/dibatalkan). Harap gunakan nomor yang berbeda.', 'pegawai_ids.required' => 'Pilih minimal satu petugas.', 'pegawai_ids.min' => 'Pilih minimal satu petugas.', ]); DB::beginTransaction(); try { $adminId = Auth::id() ?? 1; $lampiranPath = null; if ($request->hasFile('lampiran')) { $lampiranPath = $request->file('lampiran')->store('lampiran', 'public'); } $penugasan = Penugasan::create([ 'nomor_surat' => $request->nomor_surat, 'kegiatan' => $request->kegiatan, 'lokasi' => $request->lokasi, 'keterangan' => $request->keterangan, 'lampiran' => $lampiranPath, 'tanggal_mulai' => $request->tanggal_mulai, 'tanggal_selesai' => $request->tanggal_selesai, 'status_note' => $request->status_note, 'status' => 'proses', 'created_by' => $adminId, ]); // SIMPAN MULTIPLE PEGAWAI $penugasan->petugas()->sync($request->pegawai_ids); DB::commit(); return response()->json([ 'success' => true, 'message' => 'Penugasan berhasil dibuat!', 'data' => $penugasan->load('petugas') ], 201); } catch (\Exception $e) { DB::rollback(); return response()->json([ 'success' => false, 'message' => $e->getMessage() ], 500); } } /** * 3b. SHOW DETAIL TUGAS */ public function show($id) { try { $penugasan = Penugasan::with(['petugas', 'pembuat', 'penyetuju', 'progress.user'])->findOrFail($id); return response()->json([ 'success' => true, 'data' => $penugasan ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => 'Data tidak ditemukan: ' . $e->getMessage()], 404); } } /** * 3c. UPDATE TUGAS */ public function update(Request $request, $id) { $request->validate([ 'nomor_surat' => 'nullable|string|unique:penugasan,nomor_surat,' . $id, 'kegiatan' => 'required|string', 'tanggal_mulai' => 'required|date', 'pegawai_ids' => 'required|array|min:1', ], [ 'nomor_surat.unique' => 'Nomor surat ini sudah terdaftar di sistem (termasuk yang sudah dihapus/dibatalkan). Harap gunakan nomor yang berbeda.', 'pegawai_ids.required' => 'Pilih minimal satu petugas.', 'pegawai_ids.min' => 'Pilih minimal satu petugas.', ]); DB::beginTransaction(); try { $penugasan = Penugasan::findOrFail($id); $lampiranPath = $penugasan->lampiran; if ($request->hasFile('lampiran')) { if ($lampiranPath && Storage::disk('public')->exists($lampiranPath)) { Storage::disk('public')->delete($lampiranPath); } $lampiranPath = $request->file('lampiran')->store('lampiran', 'public'); } $penugasan->update([ 'nomor_surat' => $request->nomor_surat, 'kegiatan' => $request->kegiatan, 'lokasi' => $request->lokasi, 'keterangan' => $request->keterangan, 'lampiran' => $lampiranPath, 'tanggal_mulai' => $request->tanggal_mulai, 'tanggal_selesai' => $request->tanggal_selesai, 'status_note' => $request->status_note, // tidak update status/created_by ]); $penugasan->petugas()->sync($request->pegawai_ids); DB::commit(); return response()->json([ 'success' => true, 'message' => 'Penugasan berhasil diperbarui!', 'data' => $penugasan->load('petugas') ]); } catch (\Exception $e) { DB::rollback(); return response()->json(['success' => false, 'message' => $e->getMessage()], 500); } } /** * 4. UPDATE STATUS (TINJAU) */ public function updateStatus(Request $request, $id) { try { $penugasan = Penugasan::findOrFail($id); if ($request->status == 'telah_ditinjau') { $penugasan->update([ 'status' => 'telah_ditinjau', 'approved_by' => Auth::id() ?? $request->approved_by, 'approved_at' => now(), 'status_note' => $request->has('status_note') ? $request->status_note : null ]); } else { $updateData = ['status' => $request->status]; if ($request->has('status_note')) { $updateData['status_note'] = $request->status_note; } $penugasan->update($updateData); // Kirim notifikasi if (in_array($request->status, ['selesai', 'dibatalkan', 'proses'])) { $action = 'diperbarui'; if ($request->status === 'selesai') $action = 'disetujui'; if ($request->status === 'dibatalkan') $action = 'dibatalkan'; if ($request->status === 'proses') $action = 'dikembalikan (revisi)'; if ($penugasan->petugas->isNotEmpty()) { Notification::send($penugasan->petugas, new PenugasanStatusNotification($penugasan, $request->status, $action)); } } } return response()->json(['success' => true, 'message' => 'Status diperbarui!']); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()], 500); } } /** * 5. BATALKAN TUGAS */ public function destroy(Request $request, $id) { $request->validate([ 'alasan' => 'required|string' // Frontend kirim 'alasan' ]); try { $penugasan = Penugasan::findOrFail($id); $penugasan->update([ 'status' => 'dibatalkan', 'alasan_hapus' => $request->alasan ]); if ($penugasan->petugas->isNotEmpty()) { Notification::send($penugasan->petugas, new PenugasanStatusNotification($penugasan, 'dibatalkan', 'dihapus')); } return response()->json([ 'success' => true, 'message' => 'Kegiatan berhasil dibatalkan.' ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()], 500); } } /** * 6. NOTIFIKASI */ public function notifications() { // Contoh implementasi: kirim kegiatan yang sudah 'telah_ditinjau' untuk user ini try { $user = Auth::user(); $notifications = []; if ($user && $user->role === 'user') { // Pegawai id mungkin sama dengan user_id $pegawai = \App\Models\Pegawai::where('user_id', $user->id)->first(); if ($pegawai) { $notifications = $pegawai->penugasan() ->where('status', 'telah_ditinjau') ->orderBy('approved_at', 'desc') ->limit(5) ->get() ->map(function($p) { return [ 'id' => $p->id, 'text' => "Penugasan '{$p->kegiatan}' telah ditinjau.", 'is_read' => false ]; }); } } return response()->json([ 'success' => true, 'data' => $notifications ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => $e->getMessage()], 500); } } /** * 7. EXCEL EXPORT */ public function export(Request $request) { try { $columns = $request->input('columns', []); return Excel::download(new PenugasanExport($columns), 'Laporan_Penugasan.xlsx'); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => 'Export Gagal: ' . $e->getMessage()], 500); } } public function toggleArchive($id) { try { $penugasan = Penugasan::findOrFail($id); $penugasan->is_archived = !$penugasan->is_archived; $penugasan->save(); $status = $penugasan->is_archived ? 'diarsipkan' : 'dikembalikan dari arsip'; return response()->json([ 'success' => true, 'message' => "Laporan berhasil $status." ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => 'Gagal mengarsipkan laporan: ' . $e->getMessage()], 500); } } public function storeProgress(Request $request, $id) { try { $request->validate([ 'note' => 'required|string', 'attachment' => 'nullable|file|mimes:jpg,jpeg,png,pdf|max:2048' ]); $penugasan = Penugasan::findOrFail($id); $path = null; if ($request->hasFile('attachment')) { $file = $request->file('attachment'); $filename = time() . '_' . $file->getClientOriginalName(); $file->move(public_path('uploads/progress'), $filename); $path = 'uploads/progress/' . $filename; } $progress = $penugasan->progress()->create([ 'user_id' => Auth::id(), 'note' => $request->note, 'attachment' => $path ]); return response()->json([ 'success' => true, 'message' => 'Progress log added successfully', 'data' => $progress->load('user') ]); } catch (\Exception $e) { return response()->json(['success' => false, 'message' => 'Failed to add progress: ' . $e->getMessage()], 500); } } }