This commit is contained in:
Dwi Swandhana
2026-02-21 06:26:45 +07:00
parent 9f1a28ae97
commit b8a94e4508
2 changed files with 77 additions and 59 deletions
@@ -30,7 +30,7 @@ class BiorepositoryController extends Controller
$data['cabinets'] = BioCabinet::with([
'racks' => function ($query) {
$query->orderBy('level', 'ASC')->orderBy('rack_number', 'ASC');
$query->orderBy('rack_number', 'ASC')->orderBy('code', 'ASC');
},
'racks.specimens' => function ($query) {
$query->orderBy('stored_at', 'ASC');
@@ -79,10 +79,8 @@ class BiorepositoryController extends Controller
'cabinet_id' => 'required|exists:bio_cabinets,id',
'code' => 'required|max:50',
'name' => 'required|max:150',
'level' => 'required|integer|min:1',
'rack_number' => 'required|integer|min:1',
'box_number' => 'required|integer|min:1',
'capacity' => 'required|integer|min:1',
'level' => 'required|integer|min:4|max:6',
'capacity' => 'required|integer|min:16|max:20',
'notes' => 'nullable',
]);
@@ -98,13 +96,15 @@ class BiorepositoryController extends Controller
return back()->withErrors(['code' => 'Kode rack sudah dipakai pada lemari ini.'])->withInput();
}
$nextRackNumber = ((int) BioRack::where('cabinet_id', $request->input('cabinet_id'))->max('rack_number')) + 1;
BioRack::create([
'cabinet_id' => $request->input('cabinet_id'),
'code' => $request->input('code'),
'name' => $request->input('name'),
'level' => $request->input('level'),
'rack_number' => $request->input('rack_number'),
'box_number' => $request->input('box_number'),
'rack_number' => $nextRackNumber,
'box_number' => 1,
'capacity' => $request->input('capacity'),
'notes' => $request->input('notes'),
]);
@@ -135,22 +135,26 @@ class BiorepositoryController extends Controller
$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 ||
if ((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();
if ((int) $request->input('shelfnomor') < 1 || (int) $request->input('shelfnomor') > (int) $rack->level) {
return back()->withErrors(['shelfnomor' => 'Shelf melebihi konfigurasi rack.'])->withInput();
}
if ((int) $request->input('tubenomor') < 1 || (int) $request->input('tubenomor') > (int) $rack->capacity) {
return back()->withErrors(['tubenomor' => 'Nomor tube melebihi kapasitas box pada shelf ini.'])->withInput();
}
$slotTaken = BioSpecimen::where('rack_id', $rack->id)
->where('slot_number', $request->input('slotnomor'))
->where('shelf_number', $request->input('shelfnomor'))
->where('tube_number', $request->input('tubenomor'))
->exists();
if ($slotTaken) {
return back()->withErrors(['slotnomor' => 'Slot ini sudah terisi spesimen.'])->withInput();
return back()->withErrors(['tubenomor' => 'Posisi tube pada shelf ini sudah terisi spesimen.'])->withInput();
}
$atccByUser = strtoupper(preg_replace('/[^A-Za-z0-9]/', '', Session::get('nama', 'UNKNOWN')));
@@ -182,7 +186,7 @@ class BiorepositoryController extends Controller
'category_storage' => $request->input('kategorisimpan'),
'shelf_number' => $request->input('shelfnomor'),
'rack_number' => $request->input('raknomor'),
'slot_number' => $request->input('slotnomor'),
'slot_number' => $request->input('shelfnomor'),
'box_number' => $request->input('boxnomor'),
'tube_number' => $request->input('tubenomor'),
'bacteria_name' => $request->input('nmbakteri'),
@@ -34,6 +34,19 @@
gap: 6px;
margin-top: 10px;
}
.shelf-section {
border: 1px dashed #bfd3e8;
border-radius: 8px;
padding: 8px;
margin-top: 8px;
background: #fbfdff;
}
.tube-grid {
display: grid;
grid-template-columns: repeat(10, minmax(24px, 1fr));
gap: 5px;
margin-top: 8px;
}
.slot-btn {
border: 1px solid #bfd3e8;
background: #f4f9ff;
@@ -150,8 +163,8 @@
<div class="alert alert-warning m-b-20">Belum ada data spesimen.</div>
@endif
<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>
<h4 class="m-b-20">Visualisasi Lemari, Rack, Shelf, Box, dan Tube</h4>
<p class="text-muted">Klik nomor tube berwarna biru untuk mengisi spesimen. Tube merah artinya sudah terisi.</p>
@forelse($cabinets as $cabinet)
<div class="cabinet-visual">
@@ -159,39 +172,47 @@
<div class="rack-grid">
@forelse($cabinet->racks as $rack)
@php
$slotMap = [];
$tubeMap = [];
foreach ($rack->specimens as $item) {
if ($item->slot_number) {
$slotMap[$item->slot_number] = $item;
if ($item->shelf_number && $item->tube_number) {
$tubeMap[$item->shelf_number.'-'.$item->tube_number] = $item;
}
}
$capacity = (int) $rack->capacity;
$shelfCount = (int) $rack->level;
$tubeCapacity = (int) $rack->capacity;
@endphp
<div class="rack-box">
<div class="rack-header">{{ $rack->code }} - {{ $rack->name }}</div>
<div style="font-size:12px;">Shelf {{ $rack->level }} | Rack {{ $rack->rack_number ?? $rack->id }} | Box {{ $rack->box_number ?? 1 }}</div>
<div style="font-size:12px;">Slot: {{ $capacity }} | Terisi: {{ $rack->specimens->count() }}</div>
<div style="font-size:12px;">Rack No {{ $rack->rack_number ?? $rack->id }} | Total Shelf {{ $shelfCount }} | 1 Box per Shelf | Tube per Box {{ $tubeCapacity }}</div>
<div style="font-size:12px;">Terisi: {{ $rack->specimens->count() }}</div>
@if($capacity > 0)
<div class="slot-grid">
@for($i = 1; $i <= $capacity; $i++)
@if(isset($slotMap[$i]))
<div class="slot-btn slot-filled" title="{{ $slotMap[$i]->specimen_code }} - {{ $slotMap[$i]->bacteria_name ?? $slotMap[$i]->specimen_name }}">{{ $i }}</div>
@else
<button type="button"
class="slot-btn js-slot"
data-rack-id="{{ $rack->id }}"
data-shelf="{{ $rack->level }}"
data-rackno="{{ $rack->rack_number ?? $rack->id }}"
data-slot="{{ $i }}"
data-box="{{ $rack->box_number ?? 1 }}">
{{ $i }}
</button>
@endif
@endfor
</div>
@if($shelfCount > 0 && $tubeCapacity > 0)
@for($shelf = 1; $shelf <= $shelfCount; $shelf++)
<div class="shelf-section">
<div style="font-size:12px;"><b>Shelf {{ $shelf }}</b> - Box 1 (Tube 1-{{ $tubeCapacity }})</div>
<div class="tube-grid">
@for($tube = 1; $tube <= $tubeCapacity; $tube++)
@php $tubeKey = $shelf.'-'.$tube; @endphp
@if(isset($tubeMap[$tubeKey]))
<div class="slot-btn slot-filled" title="{{ $tubeMap[$tubeKey]->specimen_code }} - {{ $tubeMap[$tubeKey]->bacteria_name ?? $tubeMap[$tubeKey]->specimen_name }}">{{ $tube }}</div>
@else
<button type="button"
class="slot-btn js-slot"
data-rack-id="{{ $rack->id }}"
data-shelf="{{ $shelf }}"
data-rackno="{{ $rack->rack_number ?? $rack->id }}"
data-slot="{{ $shelf }}"
data-box="1"
data-tube="{{ $tube }}">
{{ $tube }}
</button>
@endif
@endfor
</div>
</div>
@endfor
@else
<div class="alert alert-light m-b-0 m-t-10">Kapasitas slot belum diatur.</div>
<div class="alert alert-light m-b-0 m-t-10">Konfigurasi rack belum lengkap.</div>
@endif
</div>
@empty
@@ -343,23 +364,16 @@
</div>
</div>
<div class="form-row">
<div class="form-group col-md-3">
<label>Shelf</label>
<input type="number" name="level" class="form-control" min="1" value="1" required>
<div class="form-group col-md-6">
<label>Jumlah Shelf per Rack (4-6)</label>
<input type="number" name="level" class="form-control" min="4" max="6" value="4" required>
</div>
<div class="form-group col-md-3">
<label>Rack No</label>
<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 class="form-group col-md-6">
<label>Tube per Box (16-20)</label>
<input type="number" name="capacity" class="form-control" min="16" max="20" value="16" required>
</div>
</div>
<small class="text-muted">Rack number dibuat otomatis oleh sistem. Struktur: 1 rack memiliki 4-6 shelf, setiap shelf memiliki 1 box, setiap box 16-20 tube.</small>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Batal</button>
@@ -404,16 +418,16 @@
<input type="number" class="form-control" id="raknomor" name="raknomor" readonly>
</div>
<div class="col-lg-4">
<label>Slot Number</label>
<label>Slot Number (Sama dengan Shelf)</label>
<input type="number" class="form-control" id="slotnomor" name="slotnomor" readonly>
</div>
<div class="col-lg-4">
<label>Box Number</label>
<label>Box Number (1 per Shelf)</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>
<input type="number" class="form-control" id="tubenomor" name="tubenomor" readonly required>
</div>
<div class="col-lg-4">
<label>Tanggal Simpan</label>
@@ -474,7 +488,7 @@
$('#raknomor').val($(this).data('rackno'));
$('#slotnomor').val($(this).data('slot'));
$('#boxnomor').val($(this).data('box'));
$('#tubenomor').val('');
$('#tubenomor').val($(this).data('tube'));
buildSampleCodePreview();
$('#modalIsiSlot').modal('show');
});