filled('filter')) { $filter = $request->get('filter'); if ($filter === 'expired') { // Menampilkan yang sudah lewat tanggal kadaluwarsa $query->whereNotNull('expired_at')->where('expired_at', '<', $now); } elseif ($filter === 'expiring_soon') { // Menampilkan yang akan kadaluwarsa dalam 7 hari kedepan $query->whereNotNull('expired_at') ->whereBetween('expired_at', [$now, $now->copy()->addDays(7)]); } elseif ($filter === 'low_stock') { // Menampilkan stok kritis (<= 5) $query->where('qty', '<=', 5); } } // 2. SEARCH GLOBAL if ($request->filled('search')) { $search = $request->search; $query->where(function($q) use ($search) { $q->where('nama_obat', 'like', "%{$search}%") ->orWhere('kategori', 'like', "%{$search}%") ->orWhere('unit', 'like', "%{$search}%"); }); } // 3. FILTER KATEGORI (Opsional jika ada dropdown di UI) if ($request->filled('category')) { $query->where('kategori', $request->category); } // 4. SORTING DINAMIS // Gunakan 'expired_at' sebagai default sorting jika dari Dashboard 'expired' $allowedSorts = ['nama_obat', 'kategori', 'qty', 'expired_at', 'created_at']; $sort = in_array($request->get('sort'), $allowedSorts) ? $request->get('sort') : 'nama_obat'; $direction = strtolower($request->get('direction')) === 'desc' ? 'desc' : 'asc'; $medicines = $query->orderBy($sort, $direction) ->paginate(15) ->withQueryString(); // 5. STATISTIK BOX (Selalu hitung total keseluruhan, bukan hasil filter) $totalStock = Medicine::sum('qty'); $lowStockCount = Medicine::where('qty', '<=', 5)->count(); // Menghitung barang yang sudah expired (merah) $expiredCount = Medicine::whereNotNull('expired_at') ->where('expired_at', '<', $now) ->count(); $categories = Medicine::distinct()->whereNotNull('kategori')->pluck('kategori'); return view('pages.inventory.medicines.index', compact( 'medicines', 'totalStock', 'lowStockCount', 'expiredCount', // Tambahan statistik baru 'categories' )); } /** * Tampilkan form tambah obat. */ public function create(): View { return view('pages.inventory.medicines.create'); } /** * Simpan data obat baru. */ public function store(Request $request): RedirectResponse { $validated = $request->validate([ 'nama_obat' => 'required|string|max:255', 'qty' => 'required|numeric|min:0', 'unit' => 'required|string|max:50', 'expired_date' => 'nullable|date|after_or_equal:today', 'foto_produk' => 'nullable|image|max:1024', ]); // Catatan: Logika pembuatan slug sudah dipindah ke Model Medicine (event saving) // agar Controller lebih ringkas. if ($request->hasFile('foto_produk')) { $validated['foto_produk'] = $request->file('foto_produk')->store('medicines', 'public'); } $medicine = Medicine::create($validated); $this->logAction('CREATED_MEDICINE', $medicine, "Menambah obat baru: {$medicine->nama_obat}"); // FIX: Tambahkan .index agar rute terdeteksi return redirect()->route('inventory.medicines.index')->with('success', 'Obat berhasil ditambahkan ke inventori.'); } /** * Tampilkan form edit obat. */ public function edit(Medicine $medicine): View { return view('pages.inventory.medicines.edit', compact('medicine')); } /** * Update data obat. */ public function update(Request $request, Medicine $medicine): RedirectResponse { $validated = $request->validate([ 'nama_obat' => 'required|string|max:255', 'qty' => 'required|numeric|min:0', 'unit' => 'required|string|max:50', 'expired_date' => 'nullable|date|after_or_equal:today', 'foto_produk' => 'nullable|image|max:1024', ]); if ($request->hasFile('foto_produk')) { if ($medicine->foto_produk) { Storage::disk('public')->delete($medicine->foto_produk); } $validated['foto_produk'] = $request->file('foto_produk')->store('medicines', 'public'); } $medicine->update($validated); $this->logAction('UPDATED_MEDICINE', $medicine, "Memperbarui data obat: {$medicine->nama_obat}"); // FIX: Tambahkan .index return redirect()->route('inventory.medicines.index')->with('success', 'Data obat berhasil diperbarui.'); } /** * Hapus obat. */ public function destroy(Medicine $medicine): RedirectResponse { if (auth()->user()->role !== 'admin') { abort(403, 'Hanya Admin yang diizinkan menghapus data.'); } // Proteksi Integritas rekam medis if ($medicine->treatments()->exists()) { return back()->with('error', "Gagal: Obat '{$medicine->nama_obat}' sudah tercatat di rekam medis kucing."); } if ($medicine->foto_produk) { Storage::disk('public')->delete($medicine->foto_produk); } $medicineName = $medicine->nama_obat; // PENTING: Log dilakukan SEBELUM delete $this->logAction('DELETED_MEDICINE', $medicine, "Menghapus data obat: {$medicineName}"); $medicine->delete(); // FIX: Tambahkan .index return redirect()->route('inventory.medicines.index')->with('success', 'Data obat berhasil dihapus.'); } /** * Fitur Impor Excel */ public function batchUpload(Request $request): RedirectResponse { $request->validate(['file_excel' => 'required|mimes:xlsx,xls,csv|max:2048']); try { Excel::import(new MedicineImport, $request->file('file_excel')); $this->logAction('BATCH_UPLOAD', null, "Melakukan impor massal data obat"); // FIX: Tambahkan .index return redirect()->route('inventory.medicines.index')->with('success', 'Data obat berhasil diimpor.'); } catch (\Exception $e) { return back()->with('error', 'Gagal impor: ' . $e->getMessage()); } } /** * Download Template Excel */ public function downloadTemplate() { return Excel::download(new MedicineTemplateExport, 'template_obat_earrin_cats.xlsx'); } }