From 3b61faafbd2f6aaaf9898d54fdcf4572276e9bc1 Mon Sep 17 00:00:00 2001 From: Dwi Swandhana Date: Sat, 21 Feb 2026 05:12:55 +0700 Subject: [PATCH] update --- htdocs/app/BioCabinet.php | 21 ++ htdocs/app/BioRack.php | 21 ++ htdocs/app/BioSpecimen.php | 33 ++ .../Controllers/BiorepositoryController.php | 143 +++++++++ ...02_20_000001_create_bio_cabinets_table.php | 26 ++ ...26_02_20_000002_create_bio_racks_table.php | 30 ++ ...2_20_000003_create_bio_specimens_table.php | 32 ++ .../views/admin/biorepository.blade.php | 289 ++++++++++++++++++ .../base/partials/header-plain.blade.php | 3 + htdocs/routes/web.php | 6 +- 10 files changed, 603 insertions(+), 1 deletion(-) create mode 100644 htdocs/app/BioCabinet.php create mode 100644 htdocs/app/BioRack.php create mode 100644 htdocs/app/BioSpecimen.php create mode 100644 htdocs/app/Http/Controllers/BiorepositoryController.php create mode 100644 htdocs/database/migrations/2026_02_20_000001_create_bio_cabinets_table.php create mode 100644 htdocs/database/migrations/2026_02_20_000002_create_bio_racks_table.php create mode 100644 htdocs/database/migrations/2026_02_20_000003_create_bio_specimens_table.php create mode 100644 htdocs/resources/views/admin/biorepository.blade.php diff --git a/htdocs/app/BioCabinet.php b/htdocs/app/BioCabinet.php new file mode 100644 index 00000000..bd16ed03 --- /dev/null +++ b/htdocs/app/BioCabinet.php @@ -0,0 +1,21 @@ +hasMany(BioRack::class, 'cabinet_id'); + } + + public function specimens() + { + return $this->hasMany(BioSpecimen::class, 'cabinet_id'); + } +} diff --git a/htdocs/app/BioRack.php b/htdocs/app/BioRack.php new file mode 100644 index 00000000..7dc78f17 --- /dev/null +++ b/htdocs/app/BioRack.php @@ -0,0 +1,21 @@ +belongsTo(BioCabinet::class, 'cabinet_id'); + } + + public function specimens() + { + return $this->hasMany(BioSpecimen::class, 'rack_id'); + } +} diff --git a/htdocs/app/BioSpecimen.php b/htdocs/app/BioSpecimen.php new file mode 100644 index 00000000..cee24254 --- /dev/null +++ b/htdocs/app/BioSpecimen.php @@ -0,0 +1,33 @@ +belongsTo(BioCabinet::class, 'cabinet_id'); + } + + public function rack() + { + return $this->belongsTo(BioRack::class, 'rack_id'); + } + + public function getStorageDaysAttribute() + { + if (!$this->stored_at) { + return 0; + } + + return Carbon::parse($this->stored_at)->diffInDays(Carbon::now()); + } +} diff --git a/htdocs/app/Http/Controllers/BiorepositoryController.php b/htdocs/app/Http/Controllers/BiorepositoryController.php new file mode 100644 index 00000000..27f5e714 --- /dev/null +++ b/htdocs/app/Http/Controllers/BiorepositoryController.php @@ -0,0 +1,143 @@ +get(); + $data['rackOptions'] = BioRack::with('cabinet')->orderBy('name', 'ASC')->get(); + $data['totalCabinets'] = BioCabinet::count(); + $data['totalRacks'] = BioRack::count(); + $data['totalSpecimens'] = BioSpecimen::count(); + + $data['cabinets'] = BioCabinet::with([ + 'racks' => function ($query) { + $query->orderBy('level', 'ASC')->orderBy('name', 'ASC'); + }, + 'racks.specimens' => function ($query) { + $query->orderBy('stored_at', 'ASC'); + }, + ])->orderBy('name', 'ASC')->get(); + + $data['oldestSpecimen'] = BioSpecimen::with(['cabinet', 'rack']) + ->whereNotNull('stored_at') + ->orderBy('stored_at', 'ASC') + ->first(); + + $data['oldestStorageDays'] = 0; + if ($data['oldestSpecimen']) { + $data['oldestStorageDays'] = Carbon::parse($data['oldestSpecimen']->stored_at)->diffInDays(Carbon::now()); + } + + return view('admin.biorepository', $data); + } + + public function storeCabinet(Request $request) + { + $validator = Validator::make($request->all(), [ + 'code' => 'required|max:50|unique:bio_cabinets,code', + 'name' => 'required|max:150', + 'location' => 'nullable|max:200', + 'notes' => 'nullable', + ]); + + if ($validator->fails()) { + return back()->withErrors($validator)->withInput(); + } + + BioCabinet::create([ + 'code' => $request->input('code'), + 'name' => $request->input('name'), + 'location' => $request->input('location'), + 'notes' => $request->input('notes'), + ]); + + return redirect('/biorepository')->with('success', 'Lemari biorepository berhasil ditambahkan.'); + } + + public function storeRack(Request $request) + { + $validator = Validator::make($request->all(), [ + 'cabinet_id' => 'required|exists:bio_cabinets,id', + 'code' => 'required|max:50', + 'name' => 'required|max:150', + 'level' => 'required|integer|min:1', + 'capacity' => 'nullable|integer|min:0', + 'notes' => 'nullable', + ]); + + if ($validator->fails()) { + return back()->withErrors($validator)->withInput(); + } + + $exists = BioRack::where('cabinet_id', $request->input('cabinet_id')) + ->where('code', $request->input('code')) + ->exists(); + + if ($exists) { + return back()->withErrors(['code' => 'Kode rack sudah dipakai pada lemari ini.'])->withInput(); + } + + BioRack::create([ + 'cabinet_id' => $request->input('cabinet_id'), + 'code' => $request->input('code'), + 'name' => $request->input('name'), + 'level' => $request->input('level'), + 'capacity' => $request->input('capacity') ?? 0, + 'notes' => $request->input('notes'), + ]); + + return redirect('/biorepository')->with('success', 'Rack berhasil ditambahkan.'); + } + + public function storeSpecimen(Request $request) + { + $validator = Validator::make($request->all(), [ + 'rack_id' => 'required|exists:bio_racks,id', + 'specimen_code' => 'required|max:100|unique:bio_specimens,specimen_code', + 'specimen_name' => 'required|max:200', + 'patient_name' => 'nullable|max:200', + 'collected_at' => 'nullable|date', + 'stored_at' => 'required|date', + 'volume' => 'nullable|max:50', + 'storage_condition' => 'nullable|max:100', + 'notes' => 'nullable', + ]); + + if ($validator->fails()) { + return back()->withErrors($validator)->withInput(); + } + + $rack = BioRack::find($request->input('rack_id')); + + BioSpecimen::create([ + 'cabinet_id' => $rack->cabinet_id, + 'rack_id' => $rack->id, + 'specimen_code' => $request->input('specimen_code'), + 'specimen_name' => $request->input('specimen_name'), + 'patient_name' => $request->input('patient_name'), + 'collected_at' => $request->input('collected_at'), + 'stored_at' => $request->input('stored_at'), + 'volume' => $request->input('volume'), + 'storage_condition' => $request->input('storage_condition'), + 'notes' => $request->input('notes'), + ]); + + return redirect('/biorepository')->with('success', 'Spesimen berhasil ditambahkan.'); + } +} diff --git a/htdocs/database/migrations/2026_02_20_000001_create_bio_cabinets_table.php b/htdocs/database/migrations/2026_02_20_000001_create_bio_cabinets_table.php new file mode 100644 index 00000000..2db85a52 --- /dev/null +++ b/htdocs/database/migrations/2026_02_20_000001_create_bio_cabinets_table.php @@ -0,0 +1,26 @@ +id(); + $table->string('code', 50)->unique(); + $table->string('name', 150); + $table->string('location', 200)->nullable(); + $table->text('notes')->nullable(); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent(); + }); + } + + public function down(): void + { + Schema::dropIfExists('bio_cabinets'); + } +}; diff --git a/htdocs/database/migrations/2026_02_20_000002_create_bio_racks_table.php b/htdocs/database/migrations/2026_02_20_000002_create_bio_racks_table.php new file mode 100644 index 00000000..ae830ec4 --- /dev/null +++ b/htdocs/database/migrations/2026_02_20_000002_create_bio_racks_table.php @@ -0,0 +1,30 @@ +id(); + $table->unsignedBigInteger('cabinet_id')->index(); + $table->string('code', 50); + $table->string('name', 150); + $table->integer('level')->default(1); + $table->integer('capacity')->default(0); + $table->text('notes')->nullable(); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent(); + + $table->unique(['cabinet_id', 'code']); + }); + } + + public function down(): void + { + Schema::dropIfExists('bio_racks'); + } +}; diff --git a/htdocs/database/migrations/2026_02_20_000003_create_bio_specimens_table.php b/htdocs/database/migrations/2026_02_20_000003_create_bio_specimens_table.php new file mode 100644 index 00000000..0c768010 --- /dev/null +++ b/htdocs/database/migrations/2026_02_20_000003_create_bio_specimens_table.php @@ -0,0 +1,32 @@ +id(); + $table->unsignedBigInteger('cabinet_id')->index(); + $table->unsignedBigInteger('rack_id')->index(); + $table->string('specimen_code', 100)->unique(); + $table->string('specimen_name', 200); + $table->string('patient_name', 200)->nullable(); + $table->date('collected_at')->nullable(); + $table->date('stored_at'); + $table->string('volume', 50)->nullable(); + $table->string('storage_condition', 100)->nullable(); + $table->text('notes')->nullable(); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->useCurrent(); + }); + } + + public function down(): void + { + Schema::dropIfExists('bio_specimens'); + } +}; diff --git a/htdocs/resources/views/admin/biorepository.blade.php b/htdocs/resources/views/admin/biorepository.blade.php new file mode 100644 index 00000000..c0bba1ee --- /dev/null +++ b/htdocs/resources/views/admin/biorepository.blade.php @@ -0,0 +1,289 @@ +@extends('base.layout') + +@push('styles') + +@endpush + +@section('content') +
+
+
+
+
+
+ +
+

Biorepository Lab Mikrobiologi

+
+
+
+ + @if(session('success')) +
+
+
{{ session('success') }}
+
+
+ @endif + + @if($errors->any()) +
+
+
+ @foreach($errors->all() as $err) +
{{ $err }}
+ @endforeach +
+
+
+ @endif + +
+
+
+
Total Lemari
+

{{ $totalCabinets }}

+
+
+
+
+
Total Rack
+

{{ $totalRacks }}

+
+
+
+
+
Total Spesimen
+

{{ $totalSpecimens }}

+
+
+
+ +
+
+
+
Tambah Lemari
+

+
+ @csrf +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ +
+
+
Tambah Rack
+

+
+ @csrf +
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+
+
+ +
+
+
Tambah Spesimen
+

+
+ @csrf +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+ +
+
+
+

Spesimen dengan Waktu Simpan Paling Lama

+ @if($oldestSpecimen) +
+ {{ $oldestSpecimen->specimen_code }} - {{ $oldestSpecimen->specimen_name }}
+ Lemari: {{ $oldestSpecimen->cabinet->name ?? '-' }} | Rack: {{ $oldestSpecimen->rack->name ?? '-' }}
+ Disimpan sejak: {{ $oldestSpecimen->stored_at }} ({{ $oldestStorageDays }} hari) +
+ @else +
Belum ada data spesimen.
+ @endif +
+
+
+ +
+
+
+

Visualisasi Lemari dan Rack

+ + @forelse($cabinets as $cabinet) +
+
{{ $cabinet->code }} - {{ $cabinet->name }} ({{ $cabinet->location ?? 'Lokasi belum diisi' }})
+
+ @forelse($cabinet->racks as $rack) +
+
{{ $rack->code }} - {{ $rack->name }} (Lv. {{ $rack->level }})
+
Kapasitas: {{ $rack->capacity }}
+
Total spesimen: {{ $rack->specimens->count() }}
+ + @if($rack->specimens->count() > 0) + @php + $oldestRackSpecimen = $rack->specimens->first(); + $preview = $rack->specimens->take(4); + @endphp +
Terlama di rack ini: {{ $oldestRackSpecimen->specimen_code }} ({{ $oldestRackSpecimen->storage_days }} hari)
+
Preview spesimen:
+
    + @foreach($preview as $sp) +
  • + {{ $sp->specimen_code }} - {{ $sp->specimen_name }}
    + Simpan {{ $sp->stored_at }} ({{ $sp->storage_days }} hari) +
  • + @endforeach +
+ @else +
Belum ada spesimen pada rack ini.
+ @endif +
+ @empty +
Belum ada rack pada lemari ini.
+ @endforelse +
+
+ @empty +
Belum ada data lemari biorepository.
+ @endforelse +
+
+
+
+
+@endsection diff --git a/htdocs/resources/views/base/partials/header-plain.blade.php b/htdocs/resources/views/base/partials/header-plain.blade.php index 802dafa5..4d1e02e2 100644 --- a/htdocs/resources/views/base/partials/header-plain.blade.php +++ b/htdocs/resources/views/base/partials/header-plain.blade.php @@ -65,6 +65,9 @@