update
This commit is contained in:
@@ -20,14 +20,13 @@ class BiorepositoryController extends Controller
|
|||||||
|
|
||||||
$data = [];
|
$data = [];
|
||||||
$data['cabinetOptions'] = BioCabinet::orderBy('name', 'ASC')->get();
|
$data['cabinetOptions'] = BioCabinet::orderBy('name', 'ASC')->get();
|
||||||
$data['rackOptions'] = BioRack::with('cabinet')->orderBy('name', 'ASC')->get();
|
|
||||||
$data['totalCabinets'] = BioCabinet::count();
|
$data['totalCabinets'] = BioCabinet::count();
|
||||||
$data['totalRacks'] = BioRack::count();
|
$data['totalRacks'] = BioRack::count();
|
||||||
$data['totalSpecimens'] = BioSpecimen::count();
|
$data['totalSpecimens'] = BioSpecimen::count();
|
||||||
|
|
||||||
$data['cabinets'] = BioCabinet::with([
|
$data['cabinets'] = BioCabinet::with([
|
||||||
'racks' => function ($query) {
|
'racks' => function ($query) {
|
||||||
$query->orderBy('level', 'ASC')->orderBy('name', 'ASC');
|
$query->orderBy('level', 'ASC')->orderBy('rack_number', 'ASC');
|
||||||
},
|
},
|
||||||
'racks.specimens' => function ($query) {
|
'racks.specimens' => function ($query) {
|
||||||
$query->orderBy('stored_at', 'ASC');
|
$query->orderBy('stored_at', 'ASC');
|
||||||
@@ -77,7 +76,9 @@ class BiorepositoryController extends Controller
|
|||||||
'code' => 'required|max:50',
|
'code' => 'required|max:50',
|
||||||
'name' => 'required|max:150',
|
'name' => 'required|max:150',
|
||||||
'level' => 'required|integer|min:1',
|
'level' => 'required|integer|min:1',
|
||||||
'capacity' => 'nullable|integer|min:0',
|
'rack_number' => 'required|integer|min:1',
|
||||||
|
'box_number' => 'required|integer|min:1',
|
||||||
|
'capacity' => 'required|integer|min:1',
|
||||||
'notes' => 'nullable',
|
'notes' => 'nullable',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -98,7 +99,9 @@ class BiorepositoryController extends Controller
|
|||||||
'code' => $request->input('code'),
|
'code' => $request->input('code'),
|
||||||
'name' => $request->input('name'),
|
'name' => $request->input('name'),
|
||||||
'level' => $request->input('level'),
|
'level' => $request->input('level'),
|
||||||
'capacity' => $request->input('capacity') ?? 0,
|
'rack_number' => $request->input('rack_number'),
|
||||||
|
'box_number' => $request->input('box_number'),
|
||||||
|
'capacity' => $request->input('capacity'),
|
||||||
'notes' => $request->input('notes'),
|
'notes' => $request->input('notes'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -109,14 +112,15 @@ class BiorepositoryController extends Controller
|
|||||||
{
|
{
|
||||||
$validator = Validator::make($request->all(), [
|
$validator = Validator::make($request->all(), [
|
||||||
'rack_id' => 'required|exists:bio_racks,id',
|
'rack_id' => 'required|exists:bio_racks,id',
|
||||||
'specimen_code' => 'required|max:100|unique:bio_specimens,specimen_code',
|
'kategorisimpan' => 'required|in:A,B,C,D',
|
||||||
'specimen_name' => 'required|max:200',
|
'shelfnomor' => 'required|integer|min:1',
|
||||||
'patient_name' => 'nullable|max:200',
|
'raknomor' => 'required|integer|min:1',
|
||||||
'collected_at' => 'nullable|date',
|
'slotnomor' => 'required|integer|min:1',
|
||||||
|
'boxnomor' => 'required|integer|min:1',
|
||||||
|
'tubenomor' => 'required|integer|min:1',
|
||||||
|
'nmbakteri' => 'required|max:200',
|
||||||
|
'strain' => 'required|in:Gram Negatif,Gram Positif',
|
||||||
'stored_at' => 'required|date',
|
'stored_at' => 'required|date',
|
||||||
'volume' => 'nullable|max:50',
|
|
||||||
'storage_condition' => 'nullable|max:100',
|
|
||||||
'notes' => 'nullable',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
@@ -124,20 +128,80 @@ class BiorepositoryController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$rack = BioRack::find($request->input('rack_id'));
|
$rack = BioRack::find($request->input('rack_id'));
|
||||||
|
$expectedRackNo = $rack->rack_number ?: $rack->id;
|
||||||
|
$expectedBoxNo = $rack->box_number ?: 1;
|
||||||
|
|
||||||
|
if ((int) $request->input('shelfnomor') !== (int) $rack->level ||
|
||||||
|
(int) $request->input('raknomor') !== (int) $expectedRackNo ||
|
||||||
|
(int) $request->input('boxnomor') !== (int) $expectedBoxNo) {
|
||||||
|
return back()->withErrors(['slotnomor' => 'Posisi slot tidak sesuai dengan konfigurasi rack.'])->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int) $request->input('slotnomor') > (int) $rack->capacity) {
|
||||||
|
return back()->withErrors(['slotnomor' => 'Slot melebihi kapasitas rack.'])->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
$slotTaken = BioSpecimen::where('rack_id', $rack->id)
|
||||||
|
->where('slot_number', $request->input('slotnomor'))
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($slotTaken) {
|
||||||
|
return back()->withErrors(['slotnomor' => 'Slot ini sudah terisi spesimen.'])->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
$atccByUser = strtoupper(preg_replace('/[^A-Za-z0-9]/', '', Session::get('nama', 'UNKNOWN')));
|
||||||
|
if ($atccByUser === '') {
|
||||||
|
$atccByUser = 'UNKNOWN';
|
||||||
|
}
|
||||||
|
|
||||||
|
$generatedCode = implode('-', [
|
||||||
|
$request->input('kategorisimpan'),
|
||||||
|
$request->input('shelfnomor'),
|
||||||
|
$request->input('raknomor'),
|
||||||
|
$request->input('slotnomor'),
|
||||||
|
$request->input('boxnomor'),
|
||||||
|
$atccByUser,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$baseCode = $generatedCode;
|
||||||
|
$counter = 1;
|
||||||
|
while (BioSpecimen::where('specimen_code', $generatedCode)->exists()) {
|
||||||
|
$counter++;
|
||||||
|
$generatedCode = $baseCode.'-'.$counter;
|
||||||
|
}
|
||||||
|
|
||||||
BioSpecimen::create([
|
BioSpecimen::create([
|
||||||
'cabinet_id' => $rack->cabinet_id,
|
'cabinet_id' => $rack->cabinet_id,
|
||||||
'rack_id' => $rack->id,
|
'rack_id' => $rack->id,
|
||||||
'specimen_code' => $request->input('specimen_code'),
|
'specimen_code' => $generatedCode,
|
||||||
'specimen_name' => $request->input('specimen_name'),
|
'specimen_name' => $request->input('nmbakteri'),
|
||||||
'patient_name' => $request->input('patient_name'),
|
'category_storage' => $request->input('kategorisimpan'),
|
||||||
'collected_at' => $request->input('collected_at'),
|
'shelf_number' => $request->input('shelfnomor'),
|
||||||
|
'rack_number' => $request->input('raknomor'),
|
||||||
|
'slot_number' => $request->input('slotnomor'),
|
||||||
|
'box_number' => $request->input('boxnomor'),
|
||||||
|
'tube_number' => $request->input('tubenomor'),
|
||||||
|
'bacteria_name' => $request->input('nmbakteri'),
|
||||||
|
'strain' => $request->input('strain'),
|
||||||
|
'atcc' => $atccByUser,
|
||||||
|
'input_by' => Session::get('nama'),
|
||||||
'stored_at' => $request->input('stored_at'),
|
'stored_at' => $request->input('stored_at'),
|
||||||
'volume' => $request->input('volume'),
|
'storage_condition' => $this->mapStorageCondition($request->input('kategorisimpan')),
|
||||||
'storage_condition' => $request->input('storage_condition'),
|
|
||||||
'notes' => $request->input('notes'),
|
'notes' => $request->input('notes'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return redirect('/biorepository')->with('success', 'Spesimen berhasil ditambahkan.');
|
return redirect('/biorepository')->with('success', 'Spesimen berhasil disimpan ke slot terpilih.');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function mapStorageCondition($category)
|
||||||
|
{
|
||||||
|
$mapping = [
|
||||||
|
'A' => 'Suhu Ruang',
|
||||||
|
'B' => '4 Derajat',
|
||||||
|
'C' => '20 Derajat',
|
||||||
|
'D' => '80 Derajat',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $mapping[$category] ?? 'Tidak Diketahui';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('bio_racks', function (Blueprint $table) {
|
||||||
|
if (!Schema::hasColumn('bio_racks', 'rack_number')) {
|
||||||
|
$table->integer('rack_number')->nullable()->after('level');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('bio_racks', 'box_number')) {
|
||||||
|
$table->integer('box_number')->nullable()->after('rack_number');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('bio_specimens', function (Blueprint $table) {
|
||||||
|
if (!Schema::hasColumn('bio_specimens', 'category_storage')) {
|
||||||
|
$table->string('category_storage', 1)->nullable()->after('specimen_name');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('bio_specimens', 'shelf_number')) {
|
||||||
|
$table->integer('shelf_number')->nullable()->after('category_storage');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('bio_specimens', 'rack_number')) {
|
||||||
|
$table->integer('rack_number')->nullable()->after('shelf_number');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('bio_specimens', 'slot_number')) {
|
||||||
|
$table->integer('slot_number')->nullable()->after('rack_number');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('bio_specimens', 'box_number')) {
|
||||||
|
$table->integer('box_number')->nullable()->after('slot_number');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('bio_specimens', 'tube_number')) {
|
||||||
|
$table->integer('tube_number')->nullable()->after('box_number');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('bio_specimens', 'bacteria_name')) {
|
||||||
|
$table->string('bacteria_name', 200)->nullable()->after('tube_number');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('bio_specimens', 'strain')) {
|
||||||
|
$table->string('strain', 50)->nullable()->after('bacteria_name');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('bio_specimens', 'atcc')) {
|
||||||
|
$table->string('atcc', 150)->nullable()->after('strain');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('bio_specimens', 'input_by')) {
|
||||||
|
$table->string('input_by', 150)->nullable()->after('atcc');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('bio_racks', function (Blueprint $table) {
|
||||||
|
if (Schema::hasColumn('bio_racks', 'rack_number')) {
|
||||||
|
$table->dropColumn('rack_number');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('bio_racks', 'box_number')) {
|
||||||
|
$table->dropColumn('box_number');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('bio_specimens', function (Blueprint $table) {
|
||||||
|
if (Schema::hasColumn('bio_specimens', 'category_storage')) {
|
||||||
|
$table->dropColumn('category_storage');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('bio_specimens', 'shelf_number')) {
|
||||||
|
$table->dropColumn('shelf_number');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('bio_specimens', 'rack_number')) {
|
||||||
|
$table->dropColumn('rack_number');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('bio_specimens', 'slot_number')) {
|
||||||
|
$table->dropColumn('slot_number');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('bio_specimens', 'box_number')) {
|
||||||
|
$table->dropColumn('box_number');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('bio_specimens', 'tube_number')) {
|
||||||
|
$table->dropColumn('tube_number');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('bio_specimens', 'bacteria_name')) {
|
||||||
|
$table->dropColumn('bacteria_name');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('bio_specimens', 'strain')) {
|
||||||
|
$table->dropColumn('strain');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('bio_specimens', 'atcc')) {
|
||||||
|
$table->dropColumn('atcc');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('bio_specimens', 'input_by')) {
|
||||||
|
$table->dropColumn('input_by');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
.rack-grid {
|
.rack-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.rack-box {
|
.rack-box {
|
||||||
@@ -22,21 +22,35 @@
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
min-height: 180px;
|
|
||||||
}
|
}
|
||||||
.rack-header {
|
.rack-header {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #1b3a57;
|
color: #1b3a57;
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
.specimen-preview {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 15px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.specimen-preview li {
|
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
.slot-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(8, minmax(26px, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.slot-btn {
|
||||||
|
border: 1px solid #bfd3e8;
|
||||||
|
background: #f4f9ff;
|
||||||
|
color: #134068;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 7px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.slot-btn.slot-filled {
|
||||||
|
background: #fce8e8;
|
||||||
|
border-color: #e7adad;
|
||||||
|
color: #8d1f1f;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
.oldest-highlight {
|
.oldest-highlight {
|
||||||
border: 1px solid #ffd58f;
|
border: 1px solid #ffd58f;
|
||||||
background: #fff8ea;
|
background: #fff8ea;
|
||||||
@@ -104,7 +118,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-6">
|
||||||
<div class="card-box ribbon-box">
|
<div class="card-box ribbon-box">
|
||||||
<div class="ribbon ribbon-primary">Tambah Lemari</div>
|
<div class="ribbon ribbon-primary">Tambah Lemari</div>
|
||||||
<p class="m-b-0"></p>
|
<p class="m-b-0"></p>
|
||||||
@@ -131,7 +145,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-6">
|
||||||
<div class="card-box ribbon-box">
|
<div class="card-box ribbon-box">
|
||||||
<div class="ribbon ribbon-info">Tambah Rack</div>
|
<div class="ribbon ribbon-info">Tambah Rack</div>
|
||||||
<p class="m-b-0"></p>
|
<p class="m-b-0"></p>
|
||||||
@@ -146,80 +160,38 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-row">
|
||||||
<label>Kode Rack</label>
|
<div class="form-group col-md-6">
|
||||||
<input type="text" name="code" class="form-control" placeholder="R01" required>
|
<label>Kode Rack</label>
|
||||||
</div>
|
<input type="text" name="code" class="form-control" placeholder="R01" required>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label>Nama Rack</label>
|
<div class="form-group col-md-6">
|
||||||
<input type="text" name="name" class="form-control" required>
|
<label>Nama Rack</label>
|
||||||
|
<input type="text" name="name" class="form-control" required>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group col-6">
|
<div class="form-group col-md-3">
|
||||||
<label>Level</label>
|
<label>Shelf</label>
|
||||||
<input type="number" name="level" class="form-control" min="1" value="1" required>
|
<input type="number" name="level" class="form-control" min="1" value="1" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-6">
|
<div class="form-group col-md-3">
|
||||||
<label>Kapasitas</label>
|
<label>Rack No</label>
|
||||||
<input type="number" name="capacity" class="form-control" min="0" value="0">
|
<input type="number" name="rack_number" class="form-control" min="1" value="1" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-3">
|
||||||
|
<label>Box No</label>
|
||||||
|
<input type="number" name="box_number" class="form-control" min="1" value="1" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-3">
|
||||||
|
<label>Jumlah Slot</label>
|
||||||
|
<input type="number" name="capacity" class="form-control" min="1" value="24" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-info btn-block" type="submit">Simpan Rack</button>
|
<button class="btn btn-info btn-block" type="submit">Simpan Rack</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<div class="card-box ribbon-box">
|
|
||||||
<div class="ribbon ribbon-success">Tambah Spesimen</div>
|
|
||||||
<p class="m-b-0"></p>
|
|
||||||
<form method="POST" action="{{ route('biorepository.storeSpecimen') }}">
|
|
||||||
@csrf
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Pilih Rack</label>
|
|
||||||
<select name="rack_id" class="form-control" required>
|
|
||||||
<option value="">-- Pilih --</option>
|
|
||||||
@foreach($rackOptions as $rack)
|
|
||||||
<option value="{{ $rack->id }}">{{ $rack->cabinet->code ?? '-' }} | {{ $rack->code }} - {{ $rack->name }}</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Kode Spesimen</label>
|
|
||||||
<input type="text" name="specimen_code" class="form-control" placeholder="SP-0001" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Nama Spesimen</label>
|
|
||||||
<input type="text" name="specimen_name" class="form-control" placeholder="Darah, sputum, urin" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Nama Pasien (Opsional)</label>
|
|
||||||
<input type="text" name="patient_name" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group col-6">
|
|
||||||
<label>Tanggal Ambil</label>
|
|
||||||
<input type="date" name="collected_at" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-6">
|
|
||||||
<label>Tanggal Simpan</label>
|
|
||||||
<input type="date" name="stored_at" class="form-control" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group col-6">
|
|
||||||
<label>Volume</label>
|
|
||||||
<input type="text" name="volume" class="form-control" placeholder="2 ml">
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-6">
|
|
||||||
<label>Kondisi Simpan</label>
|
|
||||||
<input type="text" name="storage_condition" class="form-control" placeholder="-20 C">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-success btn-block" type="submit">Simpan Spesimen</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -228,7 +200,7 @@
|
|||||||
<h4 class="m-b-15">Spesimen dengan Waktu Simpan Paling Lama</h4>
|
<h4 class="m-b-15">Spesimen dengan Waktu Simpan Paling Lama</h4>
|
||||||
@if($oldestSpecimen)
|
@if($oldestSpecimen)
|
||||||
<div class="oldest-highlight">
|
<div class="oldest-highlight">
|
||||||
<strong>{{ $oldestSpecimen->specimen_code }}</strong> - {{ $oldestSpecimen->specimen_name }}<br>
|
<strong>{{ $oldestSpecimen->specimen_code }}</strong> - {{ $oldestSpecimen->bacteria_name ?? $oldestSpecimen->specimen_name }}<br>
|
||||||
Lemari: {{ $oldestSpecimen->cabinet->name ?? '-' }} | Rack: {{ $oldestSpecimen->rack->name ?? '-' }}<br>
|
Lemari: {{ $oldestSpecimen->cabinet->name ?? '-' }} | Rack: {{ $oldestSpecimen->rack->name ?? '-' }}<br>
|
||||||
Disimpan sejak: {{ $oldestSpecimen->stored_at }} ({{ $oldestStorageDays }} hari)
|
Disimpan sejak: {{ $oldestSpecimen->stored_at }} ({{ $oldestStorageDays }} hari)
|
||||||
</div>
|
</div>
|
||||||
@@ -242,35 +214,48 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<div class="card-box">
|
<div class="card-box">
|
||||||
<h4 class="m-b-20">Visualisasi Lemari dan Rack</h4>
|
<h4 class="m-b-20">Visualisasi Lemari, Rack, dan Slot</h4>
|
||||||
|
<p class="text-muted">Klik slot berwarna biru untuk mengisi spesimen. Slot merah artinya sudah terisi.</p>
|
||||||
|
|
||||||
@forelse($cabinets as $cabinet)
|
@forelse($cabinets as $cabinet)
|
||||||
<div class="cabinet-visual">
|
<div class="cabinet-visual">
|
||||||
<h5 class="m-b-15">{{ $cabinet->code }} - {{ $cabinet->name }} <small class="text-muted">({{ $cabinet->location ?? 'Lokasi belum diisi' }})</small></h5>
|
<h5 class="m-b-15">{{ $cabinet->code }} - {{ $cabinet->name }} <small class="text-muted">({{ $cabinet->location ?? 'Lokasi belum diisi' }})</small></h5>
|
||||||
<div class="rack-grid">
|
<div class="rack-grid">
|
||||||
@forelse($cabinet->racks as $rack)
|
@forelse($cabinet->racks as $rack)
|
||||||
|
@php
|
||||||
|
$slotMap = [];
|
||||||
|
foreach ($rack->specimens as $item) {
|
||||||
|
if ($item->slot_number) {
|
||||||
|
$slotMap[$item->slot_number] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$capacity = (int) $rack->capacity;
|
||||||
|
@endphp
|
||||||
<div class="rack-box">
|
<div class="rack-box">
|
||||||
<div class="rack-header">{{ $rack->code }} - {{ $rack->name }} (Lv. {{ $rack->level }})</div>
|
<div class="rack-header">{{ $rack->code }} - {{ $rack->name }}</div>
|
||||||
<div>Kapasitas: {{ $rack->capacity }}</div>
|
<div style="font-size:12px;">Shelf {{ $rack->level }} | Rack {{ $rack->rack_number ?? $rack->id }} | Box {{ $rack->box_number ?? 1 }}</div>
|
||||||
<div>Total spesimen: {{ $rack->specimens->count() }}</div>
|
<div style="font-size:12px;">Slot: {{ $capacity }} | Terisi: {{ $rack->specimens->count() }}</div>
|
||||||
|
|
||||||
@if($rack->specimens->count() > 0)
|
@if($capacity > 0)
|
||||||
@php
|
<div class="slot-grid">
|
||||||
$oldestRackSpecimen = $rack->specimens->first();
|
@for($i = 1; $i <= $capacity; $i++)
|
||||||
$preview = $rack->specimens->take(4);
|
@if(isset($slotMap[$i]))
|
||||||
@endphp
|
<div class="slot-btn slot-filled" title="{{ $slotMap[$i]->specimen_code }} - {{ $slotMap[$i]->bacteria_name ?? $slotMap[$i]->specimen_name }}">{{ $i }}</div>
|
||||||
<div class="text-muted" style="font-size:12px; margin-top:4px;">Terlama di rack ini: {{ $oldestRackSpecimen->specimen_code }} ({{ $oldestRackSpecimen->storage_days }} hari)</div>
|
@else
|
||||||
<div style="margin-top:6px; font-size:12px;"><strong>Preview spesimen:</strong></div>
|
<button type="button"
|
||||||
<ul class="specimen-preview">
|
class="slot-btn js-slot"
|
||||||
@foreach($preview as $sp)
|
data-rack-id="{{ $rack->id }}"
|
||||||
<li>
|
data-shelf="{{ $rack->level }}"
|
||||||
{{ $sp->specimen_code }} - {{ $sp->specimen_name }}<br>
|
data-rackno="{{ $rack->rack_number ?? $rack->id }}"
|
||||||
<span class="text-muted">Simpan {{ $sp->stored_at }} ({{ $sp->storage_days }} hari)</span>
|
data-slot="{{ $i }}"
|
||||||
</li>
|
data-box="{{ $rack->box_number ?? 1 }}">
|
||||||
@endforeach
|
{{ $i }}
|
||||||
</ul>
|
</button>
|
||||||
|
@endif
|
||||||
|
@endfor
|
||||||
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="text-muted" style="margin-top:8px;">Belum ada spesimen pada rack ini.</div>
|
<div class="alert alert-light m-b-0 m-t-10">Kapasitas slot belum diatur.</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
@@ -286,4 +271,129 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="modalIsiSlot" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title" id="myModalLabel">Isi Spesimen ke Slot</h4>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="{{ route('biorepository.storeSpecimen') }}">
|
||||||
|
@csrf
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" id="rack_id" name="rack_id">
|
||||||
|
|
||||||
|
<div class="form-group m-b-25">
|
||||||
|
<div class="col-12">
|
||||||
|
<label>Category Penyimpanan</label>
|
||||||
|
<select class="form-control" id="kategorisimpan" name="kategorisimpan">
|
||||||
|
<option value="A">Penyimpanan Suhu Ruang</option>
|
||||||
|
<option value="B">Penyimpanan Suhu 4 Derajat</option>
|
||||||
|
<option value="C">Penyimpanan Suhu 20 Derajat</option>
|
||||||
|
<option value="D">Penyimpanan Suhu 80 Derajat</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<label>Shelf Nomor</label>
|
||||||
|
<input type="number" class="form-control" id="shelfnomor" name="shelfnomor" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<label>Rack Number</label>
|
||||||
|
<input type="number" class="form-control" id="raknomor" name="raknomor" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<label>Slot Number</label>
|
||||||
|
<input type="number" class="form-control" id="slotnomor" name="slotnomor" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<label>Box Number</label>
|
||||||
|
<input type="number" class="form-control" id="boxnomor" name="boxnomor" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<label>Tube Number</label>
|
||||||
|
<input type="number" class="form-control" id="tubenomor" name="tubenomor" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<label>Tanggal Simpan</label>
|
||||||
|
<input type="date" class="form-control" id="stored_at" name="stored_at" value="{{ date('Y-m-d') }}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group m-b-25">
|
||||||
|
<div class="col-12">
|
||||||
|
<label>Bactery Name</label>
|
||||||
|
<input type="text" class="form-control" id="nmbakteri" name="nmbakteri" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group m-b-25">
|
||||||
|
<div class="col-12">
|
||||||
|
<label>Strain</label>
|
||||||
|
<select class="form-control" id="strain" name="strain">
|
||||||
|
<option value="Gram Negatif">Gram Negatif</option>
|
||||||
|
<option value="Gram Positif">Gram Positif</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group m-b-25">
|
||||||
|
<div class="col-12">
|
||||||
|
<label>ATCC (Otomatis dari User Login)</label>
|
||||||
|
<input type="text" class="form-control" id="atcc" name="atcc" value="{{ Session('nama') }}" readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group m-b-0">
|
||||||
|
<div class="col-12">
|
||||||
|
<label>Sample Code (Preview Otomatis)</label>
|
||||||
|
<input type="text" class="form-control" id="samplecodepreview" readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Simpan Spesimen</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@push('script')
|
||||||
|
<script>
|
||||||
|
function normalizeCode(value) {
|
||||||
|
return (value || '').toString().toUpperCase().replace(/[^A-Z0-9]/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSampleCodePreview() {
|
||||||
|
var category = $('#kategorisimpan').val() || '';
|
||||||
|
var shelf = $('#shelfnomor').val() || '';
|
||||||
|
var rack = $('#raknomor').val() || '';
|
||||||
|
var slot = $('#slotnomor').val() || '';
|
||||||
|
var box = $('#boxnomor').val() || '';
|
||||||
|
var atcc = normalizeCode($('#atcc').val());
|
||||||
|
var code = [category, shelf, rack, slot, box, atcc].join('-');
|
||||||
|
$('#samplecodepreview').val(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('click', '.js-slot', function () {
|
||||||
|
$('#rack_id').val($(this).data('rack-id'));
|
||||||
|
$('#shelfnomor').val($(this).data('shelf'));
|
||||||
|
$('#raknomor').val($(this).data('rackno'));
|
||||||
|
$('#slotnomor').val($(this).data('slot'));
|
||||||
|
$('#boxnomor').val($(this).data('box'));
|
||||||
|
$('#tubenomor').val('');
|
||||||
|
buildSampleCodePreview();
|
||||||
|
$('#modalIsiSlot').modal('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('keyup change', '#kategorisimpan, #shelfnomor, #raknomor, #slotnomor, #boxnomor, #atcc', function () {
|
||||||
|
buildSampleCodePreview();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
Reference in New Issue
Block a user