(lpj/nilai-plafond): Tambah field biaya, validasi, transaksi DB, ekspor, dan tampilan

- Menambahkan kolom biaya ke seluruh alur Nilai Plafond (model, request, controller, views, export, dan migrasi)
- Update model NilaiPlafond agar field biaya bisa di-mass assign ($fillable)
- Tambah validasi baru 'biaya' (nullable|numeric|min:0) di NilaiPlafondRequest
- Terapkan transaksi DB (beginTransaction, commit, rollback) pada store/update/destroy di controller
- Tambahkan kolom biaya ke view create, edit, dan datatable index dengan format Rupiah dan tooltip nilai mentah
- Tambah header & mapping kolom biaya di NilaiPlafondExport agar muncul di hasil export Excel
- Tambah migrasi kolom biaya bertipe decimal(15,2) nullable dengan rollback support
- Tambahkan logging detail (Log::info & Log::error) di setiap proses utama controller
- Pastikan pencarian kolom biaya pada datatables menggunakan CAST ke TEXT untuk kompatibilitas PostgreSQL
This commit is contained in:
Daeng Deni Mardaeni
2025-10-03 10:23:21 +07:00
parent 04ee3a0c48
commit e773b82218
7 changed files with 221 additions and 62 deletions

View File

@@ -5,54 +5,68 @@
@endsection
@section('content')
<div class="w-full grid gap-5 lg:gap-7.5 mx-auto">
@if(isset($nilaiPlafond->id))
<div class="grid gap-5 mx-auto w-full lg:gap-7.5">
@if (isset($nilaiPlafond->id))
<form action="{{ route('basicdata.nilai-plafond.update', $nilaiPlafond->id) }}" method="POST">
<input type="hidden" name="id" value="{{ $nilaiPlafond->id }}">
@method('PUT')
@else
<form method="POST" action="{{ route('basicdata.nilai-plafond.store') }}">
@endif
@csrf
<div class="card border border-agi-100 pb-2.5">
<div class="card-header bg-agi-50" id="basic_settings">
<h3 class="card-title">
{{ isset($nilaiPlafond->id) ? 'Edit' : 'Tambah' }} Nilai Plafond
</h3>
<div class="flex items-center gap-2">
<a href="{{ route('basicdata.nilai-plafond.index') }}" class="btn btn-xs btn-info"><i class="ki-filled ki-exit-left"></i> Back</a>
</div>
</div>
<div class="card-body grid gap-5">
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Code
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="input @error('code') border-danger bg-danger-light @enderror" type="text" name="code" value="{{ $nilaiPlafond->code ?? '' }}">
@error('code')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Name
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="input @error('name') border-danger bg-danger-light @enderror" type="text" name="name" value="{{ $nilaiPlafond->name ?? '' }}">
@error('name')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex justify-end">
<button type="submit" class="btn btn-primary">
Save
</button>
</div>
</div>
</div>
</form>
@else
<form method="POST" action="{{ route('basicdata.nilai-plafond.store') }}">
@endif
@csrf
<div class="pb-2.5 border card border-agi-100">
<div class="card-header bg-agi-50" id="basic_settings">
<h3 class="card-title">
{{ isset($nilaiPlafond->id) ? 'Edit' : 'Tambah' }} Nilai Plafond
</h3>
<div class="flex gap-2 items-center">
<a href="{{ route('basicdata.nilai-plafond.index') }}" class="btn btn-xs btn-info"><i
class="ki-filled ki-exit-left"></i> Back</a>
</div>
</div>
<div class="grid gap-5 card-body">
<div class="flex flex-wrap gap-2.5 items-baseline lg:flex-nowrap">
<label class="form-label max-w-56">
Code
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="input @error('code') border-danger bg-danger-light @enderror" type="text"
name="code" value="{{ $nilaiPlafond->code ?? '' }}">
@error('code')
<em class="text-sm alert text-danger">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex flex-wrap gap-2.5 items-baseline lg:flex-nowrap">
<label class="form-label max-w-56">
Name
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="input @error('name') border-danger bg-danger-light @enderror" type="text"
name="name" value="{{ $nilaiPlafond->name ?? '' }}">
@error('name')
<em class="text-sm alert text-danger">{{ $message }}</em>
@enderror
</div>
</div>
{{-- Field Biaya --}}
<div class="mt-3">
<label class="text-gray-900 form-label">Biaya</label>
<input class="input @error('biaya') border-danger bg-danger-light @enderror" type="number"
step="0.01" name="biaya" value="{{ $nilaiPlafond->biaya ?? old('biaya') }}" placeholder="0">
@error('biaya')
<div class="text-danger">{{ $message }}</div>
@enderror
<div class="mt-1 text-xs text-muted">Contoh: 1500000 untuk satu juta lima ratus ribu</div>
</div>
</div>
<div class="flex justify-end">
<button type="submit" class="btn btn-primary">
Save
</button>
</div>
</div>
</div>
</form>
</div>
@endsection

View File

@@ -6,8 +6,8 @@
@section('content')
<div class="grid">
<div class="card border border-agi-100 card-grid min-w-full" data-datatable="false" data-datatable-page-size="10" data-datatable-state-save="false" id="nilai-plafond-table" data-api-url="{{ route('basicdata.nilai-plafond.datatables') }}">
<div class="card-header bg-agi-50 py-5 flex-wrap">
<div class="min-w-full border card border-agi-100 card-grid" data-datatable="false" data-datatable-page-size="10" data-datatable-state-save="false" id="nilai-plafond-table" data-api-url="{{ route('basicdata.nilai-plafond.datatables') }}">
<div class="flex-wrap py-5 card-header bg-agi-50">
<h3 class="card-title">
Daftar Nilai Plafond
</h3>
@@ -26,7 +26,7 @@
</div>
<div class="card-body">
<div class="scrollable-x-auto">
<table class="table table-auto table-border align-middle text-gray-700 font-medium text-sm" data-datatable-table="true">
<table class="table text-sm font-medium text-gray-700 align-middle table-auto table-border" data-datatable-table="true">
<thead>
<tr>
<th class="w-14">
@@ -40,17 +40,21 @@
<span class="sort"> <span class="sort-label"> Nilai Plafond </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[250px]" data-datatable-column="biaya">
<span class="sort"> <span class="sort-label"> Biaya </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[50px] text-center" data-datatable-column="actions">Action</th>
</tr>
</thead>
</table>
</div>
<div class="card-footer justify-center md:justify-between flex-col md:flex-row gap-3 text-gray-600 text-2sm font-medium">
<div class="flex items-center gap-2">
<div class="flex-col gap-3 justify-center font-medium text-gray-600 card-footer md:justify-between md:flex-row text-2sm">
<div class="flex gap-2 items-center">
Show
<select class="select select-sm w-16" data-datatable-size="true" name="perpage"> </select> per page
<select class="w-16 select select-sm" data-datatable-size="true" name="perpage"> </select> per page
</div>
<div class="flex items-center gap-4">
<div class="flex gap-4 items-center">
<span data-datatable-info="true"> </span>
<div class="pagination" data-datatable-pagination="true">
</div>
@@ -119,6 +123,19 @@
name: {
title: 'Nilai Plafond',
},
biaya: {
title: 'Biaya',
// formatter: format rupiah
render: (data) => {
try {
const raw = Number(data.biaya ?? 0);
const formatted = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(raw);
return `<span title="${raw}">${formatted}</span>`;
} catch (e) {
return data?.biaya ?? '-';
}
}
},
actions: {
title: 'Status',
render: (item, data) => {