Initial commit: Struktur aplikasi ilycats

This commit is contained in:
Dwi F Sukmana
2026-02-08 15:16:35 +07:00
commit 0921901aed
235 changed files with 29016 additions and 0 deletions
+259
View File
@@ -0,0 +1,259 @@
<?php
namespace App\Http\Controllers;
use App\Models\{Cat, User, Matting};
use App\Traits\LogsActivity;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\View\View;
use Illuminate\Support\Facades\{Storage, DB, Gate}; // Tambahkan Gate
use Illuminate\Support\Carbon;
use Barryvdh\DomPDF\Facade\Pdf;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
use Intervention\Image\Laravel\Facades\Image;
class CatController extends Controller
{
use LogsActivity;
/**
* Menampilkan daftar kucing dengan filter cerdas.
*/
public function index(Request $request): View
{
// Proteksi Akses: Read
Gate::authorize('check-access', ['cats', 'read']);
$query = Cat::query()->with(['pic', 'sire', 'dam']);
// 1. Pencarian Global
if ($request->filled('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('nama_kucing', 'like', "%{$search}%")
->orWhere('ras', 'like', "%{$search}%")
->orWhere('microchip', 'like', "%{$search}%")
->orWhere('pedigree', 'like', "%{$search}%")
->orWhere('kode_ems', 'like', "%{$search}%");
});
}
// 2. Filter Spesifik
if ($request->filled('status')) $query->where('status', $request->status);
if ($request->filled('ras')) $query->where('ras', $request->ras);
if ($request->filled('gender')) $query->where('jenis_kelamin', $request->gender);
$cats = $query->latest()->paginate(15)->withQueryString();
// 3. Statistik Dashboard (Tetap Utuh)
$totalCats = Cat::count();
$safeWords = ['sehat', 'normal', 'aktif', 'sembuh', '-', 'tidak ada', 'clean'];
$sickCats = Cat::whereNotNull('riwayat_sakit_terakhir')
->where(function($q) use ($safeWords) {
foreach ($safeWords as $word) {
$q->where('riwayat_sakit_terakhir', 'NOT LIKE', "%{$word}%");
}
})->count();
$breeds = Cat::distinct()->pluck('ras');
return view('pages.cats.index', compact('cats', 'totalCats', 'sickCats', 'breeds'));
}
public function create(): View
{
// Proteksi Akses: Create
Gate::authorize('check-access', ['cats', 'create']);
$sires = Cat::where('jenis_kelamin', 'Jantan')->get(['id', 'nama_kucing']);
$dams = Cat::where('jenis_kelamin', 'Betina')->get(['id', 'nama_kucing']);
$users = User::all(['id', 'name']);
return view('pages.cats.create', compact('sires', 'dams', 'users'));
}
public function store(Request $request): RedirectResponse
{
Gate::authorize('check-access', ['cats', 'create']);
$validated = $this->validateCat($request);
return DB::transaction(function () use ($request, $validated) {
if ($request->hasFile('foto')) {
$validated['foto'] = $this->handleImageUpload($request->file('foto'));
}
if (isset($validated['berat_badan'])) {
$validated['berat_terakhir'] = $validated['berat_badan'];
}
if (empty($validated['status'])) {
$validated['status'] = ($request->induk_jantan_id || $request->induk_betina_id) ? 'Kitten' : 'Active';
}
// Memastikan registry disimpan sebagai JSON jika model tidak melakukan casting
if (isset($validated['registry'])) {
$validated['registry'] = $validated['registry'];
}
$cat = Cat::create($validated);
$this->logAction('CREATED', $cat, "Mendaftarkan pasien baru: {$cat->nama_kucing}", 'Cat');
return redirect()->route('cats.index')->with('success', "{$cat->nama_kucing} berhasil didaftarkan.");
});
}
public function show(Cat $cat): View
{
Gate::authorize('check-access', ['cats', 'read']);
$cat->load([
'treatments' => fn($q) => $q->latest(),
'sire', 'dam', 'pic',
'weightHistories' => fn($q) => $q->latest()->limit(12)
]);
return view('pages.cats.show', compact('cat'));
}
public function edit(Cat $cat): View
{
// Proteksi: Akses Update
Gate::authorize('check-access', ['cats', 'update']);
$sires = Cat::where('jenis_kelamin', 'Jantan')->where('id', '!=', $cat->id)->get(['id', 'nama_kucing']);
$dams = Cat::where('jenis_kelamin', 'Betina')->where('id', '!=', $cat->id)->get(['id', 'nama_kucing']);
$users = User::all(['id', 'name']);
return view('pages.cats.edit', compact('cat', 'sires', 'dams', 'users'));
}
public function update(Request $request, Cat $cat): RedirectResponse
{
Gate::authorize('check-access', ['cats', 'update']);
$validated = $this->validateCat($request, $cat->id);
return DB::transaction(function () use ($request, $validated, $cat) {
if ($request->hasFile('foto')) {
if ($cat->foto) Storage::disk('public')->delete($cat->foto);
$validated['foto'] = $this->handleImageUpload($request->file('foto'));
}
if ($request->filled('berat_badan')) {
$validated['berat_terakhir'] = $request->berat_badan;
if ($cat->berat_badan != $request->berat_badan) {
$cat->weightHistories()->create(['berat' => $request->berat_badan]);
}
}
$cat->update($validated);
$this->logAction('UPDATED', $cat, "Update data medis/profil: {$cat->nama_kucing}", 'Cat');
return redirect()->route('cats.index')->with('success', 'Data berhasil diperbarui.');
});
}
public function destroy(Cat $cat): RedirectResponse
{
Gate::authorize('check-access', ['cats', 'delete']);
$isParent = Matting::where('sire_id', $cat->id)->orWhere('dam_id', $cat->id)->exists();
if ($isParent) {
return back()->with('error', "Gagal! {$cat->nama_kucing} tidak bisa dihapus karena sudah menjadi Induk.");
}
return DB::transaction(function () use ($cat) {
$name = $cat->nama_kucing;
if ($cat->foto) Storage::disk('public')->delete($cat->foto);
$this->logAction('DELETED', $cat, "Menghapus data permanen: {$name}", 'Cat');
$cat->delete();
return redirect()->route('cats.index')->with('success', "Data {$name} telah dihapus.");
});
}
public function getCatData($id): JsonResponse
{
$cat = Cat::find($id);
if (!$cat) return response()->json(['error' => 'Not found'], 404);
return response()->json([
'tgl_lahir' => $cat->tanggal_lahir ? $cat->tanggal_lahir->format('Y-m-d') : '',
'pedigree' => $cat->pedigree ?? '',
'microchip' => $cat->microchip ?? '',
'kode_ems' => $cat->kode_ems ?? '',
]);
}
/**
* PDF ID Card / Medical Pass
* PENYEMPURNAAN: Perbaikan Barcode (Base64 SVG)
*/
public function downloadPdf(Cat $cat)
{
Gate::authorize('check-access', ['cats', 'read']);
$cat->load(['pic', 'sire', 'dam']);
// FIX: Konversi SVG ke Base64 agar tampil di domPDF tanpa Imagick
$qrCodeRaw = QrCode::format('svg')
->size(150)
->margin(1)
->generate(route('cats.show', $cat->id));
$qrCode = base64_encode($qrCodeRaw);
$pdf = Pdf::loadView('pages.cats.pdf-card', [
'cat' => $cat,
'qrCode' => $qrCode, // Dikirim sebagai Base64
'printedAt' => Carbon::now()->translatedFormat('d F Y H:i')
]);
return $pdf->setPaper([0, 0, 300, 500], 'portrait')
->stream('MEDICAL_PASS_' . strtoupper($cat->nama_kucing) . '.pdf');
}
/* -------------------------------------------------------------------------- */
/* HELPERS (TETAP UTUH) */
/* -------------------------------------------------------------------------- */
private function validateCat(Request $request, $id = null)
{
return $request->validate([
'nama_kucing' => 'required|string|max:255',
'ras' => 'required|string',
'jenis_kelamin' => 'required|in:Jantan,Betina',
'tanggal_lahir' => 'nullable|date',
'foto' => 'nullable|image|mimes:jpg,jpeg,png|max:3072',
'registry' => 'nullable|array',
'registry.*' => 'string',
'pedigree' => 'nullable|string|max:255',
'microchip' => 'nullable|string|max:255',
'kode_ems' => 'nullable|string|max:255',
'status' => 'nullable|string',
'induk_jantan_id' => 'nullable|exists:cats,id',
'induk_betina_id' => 'nullable|exists:cats,id',
'lokasi_kandang' => 'nullable|string',
'pic_id' => 'nullable|exists:users,id',
'berat_badan' => 'nullable|numeric|min:0',
'riwayat_sakit_terakhir' => 'nullable|string',
'jadwal_vaksin' => 'nullable|date',
'jadwal_obat_cacing' => 'nullable|date',
'jadwal_obat_kutu' => 'nullable|date',
'jadwal_grooming' => 'nullable|date',
]);
}
private function handleImageUpload($file): string
{
$filename = 'cat_' . uniqid() . '.png';
$path = 'cats/' . $filename;
$img = Image::read($file)->cover(500, 500, 'center')->toPng();
Storage::disk('public')->put($path, (string) $img);
return $path;
}
}