filled('status')) { $query->byStatus($request->status); } // Filter by teknisi if ($request->filled('teknisi_id')) { $query->byTeknisi($request->teknisi_id); } // Filter by date range if ($request->filled('start_date') || $request->filled('end_date')) { $query->byDateRange($request->start_date, $request->end_date); } // Search functionality if ($request->filled('search')) { $search = $request->search; $query->where(function($q) use ($search) { $q->where('tugas', 'like', "%{$search}%") ->orWhere('deskripsi_progres', 'like', "%{$search}%") ->orWhereHas('teknisi', function($teknisiQuery) use ($search) { $teknisiQuery->where('nama', 'like', "%{$search}%"); }); }); } $progresKerja = $query->latest()->paginate(10); // Get filter options $teknisiList = Teknisi::orderBy('nama')->get(); $statusList = ProgresKerja::$statusProgres; // Get statistics $statistics = ProgresKerja::getStatistics(); return view('Admin.KelolaPekerjaan.ProgresKerja', compact( 'progresKerja', 'teknisiList', 'statusList', 'statistics' )); } /** * Show the form for creating a new progress kerja. * * @return \Illuminate\Http\Response */ public function create() { $penugasanList = Penugasan::with('pelanggan')->orderBy('created_at', 'desc')->get(); $teknisiList = Teknisi::orderBy('nama')->get(); $statusList = ProgresKerja::$statusProgres; return view('Admin.KelolaPekerjaan.ProgresKerjaForm', compact( 'penugasanList', 'teknisiList', 'statusList' )); } /** * Store a newly created progress kerja in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { $validator = Validator::make($request->all(), [ 'id_penugasan' => 'required|exists:penugasan,id_penugasan', 'id_teknisi' => 'required|exists:teknisi,id_teknisi', 'tugas' => 'required|string|max:255', 'deskripsi_progres' => 'nullable|string', 'status_progres' => 'required|in:' . implode(',', array_keys(ProgresKerja::$statusProgres)), 'persentase_pekerjaan' => 'required|integer|min:0|max:100', 'foto_progress' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', ]); if ($validator->fails()) { return redirect()->back() ->withErrors($validator) ->withInput(); } $data = $request->only([ 'id_penugasan', 'id_teknisi', 'tugas', 'deskripsi_progres', 'status_progres', 'persentase_pekerjaan' ]); // Handle foto progress upload if ($request->hasFile('foto_progress')) { $file = $request->file('foto_progress'); $fileName = time() . '_' . Str::random(10) . '.' . $file->getClientOriginalExtension(); // Store in public/storage/progress_photos $file->storeAs('progress_photos', $fileName, 'public'); $data['foto_progress'] = $fileName; } $progresKerja = ProgresKerja::create($data); // Auto update status based on percentage $progresKerja->autoUpdateStatus(); return redirect()->route('progres-kerja.index') ->with('success', 'Progress kerja berhasil ditambahkan.'); } /** * Display the specified progress kerja. * * @param \App\Models\ProgresKerja $progresKerja * @return \Illuminate\Http\Response */ public function show(ProgresKerja $progresKerja) { $progresKerja->load(['teknisi', 'penugasan.pelanggan']); return view('Admin.KelolaPekerjaan.ProgresKerjaDetail', compact('progresKerja')); } /** * Show the form for editing the specified progress kerja. * * @param \App\Models\ProgresKerja $progresKerja * @return \Illuminate\Http\Response */ public function edit(ProgresKerja $progresKerja) { $penugasanList = Penugasan::with('pelanggan')->orderBy('created_at', 'desc')->get(); $teknisiList = Teknisi::orderBy('nama')->get(); $statusList = ProgresKerja::$statusProgres; return view('Admin.KelolaPekerjaan.ProgresKerjaForm', compact( 'progresKerja', 'penugasanList', 'teknisiList', 'statusList' )); } /** * Update the specified progress kerja in storage. * * @param \Illuminate\Http\Request $request * @param \App\Models\ProgresKerja $progresKerja * @return \Illuminate\Http\Response */ public function update(Request $request, ProgresKerja $progresKerja) { $validator = Validator::make($request->all(), [ 'id_penugasan' => 'required|exists:penugasan,id_penugasan', 'id_teknisi' => 'required|exists:teknisi,id_teknisi', 'tugas' => 'required|string|max:255', 'deskripsi_progres' => 'nullable|string', 'status_progres' => 'required|in:' . implode(',', array_keys(ProgresKerja::$statusProgres)), 'persentase_pekerjaan' => 'required|integer|min:0|max:100', 'foto_progress' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', ]); if ($validator->fails()) { return redirect()->back() ->withErrors($validator) ->withInput(); } $data = $request->only([ 'id_penugasan', 'id_teknisi', 'tugas', 'deskripsi_progres', 'status_progres', 'persentase_pekerjaan' ]); // Handle foto progress upload if ($request->hasFile('foto_progress')) { // Delete old photo if exists if ($progresKerja->foto_progress) { Storage::disk('public')->delete('progress_photos/' . $progresKerja->foto_progress); } $file = $request->file('foto_progress'); $fileName = time() . '_' . Str::random(10) . '.' . $file->getClientOriginalExtension(); $file->storeAs('progress_photos', $fileName, 'public'); $data['foto_progress'] = $fileName; } $progresKerja->update($data); // Auto update status based on percentage $progresKerja->autoUpdateStatus(); return redirect()->route('progres-kerja.index') ->with('success', 'Progress kerja berhasil diperbarui.'); } /** * Remove the specified progress kerja from storage. * * @param \App\Models\ProgresKerja $progresKerja * @return \Illuminate\Http\Response */ public function destroy(ProgresKerja $progresKerja) { // Delete photo if exists if ($progresKerja->foto_progress) { Storage::disk('public')->delete('progress_photos/' . $progresKerja->foto_progress); } $progresKerja->delete(); return redirect()->route('progres-kerja.index') ->with('success', 'Progress kerja berhasil dihapus.'); } /** * Quick update progress status * * @param \Illuminate\Http\Request $request * @param \App\Models\ProgresKerja $progresKerja * @return \Illuminate\Http\JsonResponse */ public function updateStatus(Request $request, ProgresKerja $progresKerja) { $validator = Validator::make($request->all(), [ 'status' => 'required|in:' . implode(',', array_keys(ProgresKerja::$statusProgres)), ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Status tidak valid.' ], 400); } // Check if transition is valid if (!$progresKerja->canTransitionTo($request->status)) { return response()->json([ 'success' => false, 'message' => 'Transisi status tidak valid.' ], 400); } $progresKerja->update(['status_progres' => $request->status]); return response()->json([ 'success' => true, 'message' => 'Status berhasil diperbarui.', 'new_status' => $progresKerja->status_formatted, 'badge_class' => $progresKerja->status_badge_class ]); } /** * Update progress percentage * * @param \Illuminate\Http\Request $request * @param \App\Models\ProgresKerja $progresKerja * @return \Illuminate\Http\JsonResponse */ public function updatePercentage(Request $request, ProgresKerja $progresKerja) { $validator = Validator::make($request->all(), [ 'percentage' => 'required|integer|min:0|max:100', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Persentase tidak valid.' ], 400); } $progresKerja->update(['persentase_pekerjaan' => $request->percentage]); $progresKerja->autoUpdateStatus(); return response()->json([ 'success' => true, 'message' => 'Persentase berhasil diperbarui.', 'new_percentage' => $progresKerja->persentase_pekerjaan, 'new_status' => $progresKerja->status_formatted, 'badge_class' => $progresKerja->status_badge_class ]); } /** * Get progress by teknisi (AJAX) * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\JsonResponse */ public function getByTeknisi(Request $request) { $teknisiId = $request->get('teknisi_id'); if (!$teknisiId) { return response()->json([ 'success' => false, 'message' => 'Teknisi ID diperlukan.' ], 400); } $progresKerja = ProgresKerja::getByTeknisi($teknisiId); return response()->json([ 'success' => true, 'data' => $progresKerja->map(function($item) { return [ 'id' => $item->id_progres, 'tugas' => $item->tugas, 'status' => $item->status_formatted, 'persentase' => $item->persentase_pekerjaan, 'tanggal_update' => $item->tanggal_update_formatted, 'penugasan' => $item->penugasan->nama_pekerjaan ?? 'N/A' ]; }) ]); } /** * Export progress data * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function export(Request $request) { $query = ProgresKerja::with(['teknisi', 'penugasan']); // Apply same filters as index if ($request->filled('status')) { $query->byStatus($request->status); } if ($request->filled('teknisi_id')) { $query->byTeknisi($request->teknisi_id); } if ($request->filled('start_date') || $request->filled('end_date')) { $query->byDateRange($request->start_date, $request->end_date); } $progresKerja = $query->latest()->get(); $filename = 'progress_kerja_' . date('Y-m-d') . '.csv'; $headers = [ 'Content-Type' => 'text/csv', 'Content-Disposition' => 'attachment; filename="' . $filename . '"', ]; $callback = function() use ($progresKerja) { $file = fopen('php://output', 'w'); // Add BOM for UTF-8 fwrite($file, "\xEF\xBB\xBF"); // Header row fputcsv($file, [ 'ID', 'Penugasan', 'Teknisi', 'Tugas', 'Deskripsi Progress', 'Status', 'Persentase (%)', 'Tanggal Update', 'Foto Progress' ]); // Data rows foreach ($progresKerja as $item) { fputcsv($file, [ $item->id_progres, $item->penugasan->nama_pekerjaan ?? 'N/A', $item->teknisi->nama ?? 'N/A', $item->tugas, $item->deskripsi_progres, $item->status_formatted, $item->persentase_pekerjaan, $item->tanggal_update_formatted, $item->has_foto ? 'Ya' : 'Tidak' ]); } fclose($file); }; return response()->stream($callback, 200, $headers); } /** * Get recent updates for dashboard * * @return \Illuminate\Http\JsonResponse */ public function getRecentUpdates() { $recentUpdates = ProgresKerja::getRecentUpdates(5); return response()->json([ 'success' => true, 'data' => $recentUpdates->map(function($item) { return [ 'id' => $item->id_progres, 'tugas' => $item->tugas, 'teknisi' => $item->teknisi->nama ?? 'N/A', 'status' => $item->status_formatted, 'persentase' => $item->persentase_pekerjaan, 'tanggal_update' => $item->tanggal_update_formatted, 'days_since_update' => $item->days_since_update ]; }) ]); } /** * Get statistics for dashboard * * @return \Illuminate\Http\JsonResponse */ public function getStatistics() { $statistics = ProgresKerja::getStatistics(); return response()->json([ 'success' => true, 'data' => $statistics ]); } }