partial update create page quick search

This commit is contained in:
arifal
2025-05-05 18:36:56 +07:00
parent d95676d477
commit 2618ac06d0
10 changed files with 663 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers;
use App\Enums\PbgTaskApplicationTypes;
use App\Enums\PbgTaskStatus;
use App\Models\PbgTask;
use Illuminate\Http\Request;
use function Illuminate\Log\log;
class QuickSearchController extends Controller
{
public function index(){
return view("quick-search.index");
}
public function search_result(Request $request){
$keyword = $request->get("keyword");
return view('quick-search.result', compact('keyword'));
}
public function quick_search_datatable(Request $request)
{
try {
$query = PbgTask::orderBy('id', 'desc');
if ($request->filled('search')) {
$search = $request->get('search');
$query->where(function ($q) use ($search) {
$q->where('name', 'LIKE', "%$search%")
->orWhere('registration_number', 'LIKE', "%$search%")
->orWhere('address', 'LIKE', "%$search%")
->orWhere('document_number', 'LIKE', "%$search%");
});
}
return response()->json($query->paginate());
} catch (\Throwable $e) {
\Log::error("Error fetching datatable data: " . $e->getMessage());
return response()->json([
'message' => 'Terjadi kesalahan saat mengambil data.',
'error' => $e->getMessage(),
], 500);
}
}
public function show($id)
{
try {
$data = PbgTask::with([
'pbg_task_retributions',
'pbg_task_index_integrations',
'pbg_task_retributions.pbg_task_prasarana'
])->findOrFail($id);
$statusOptions = PbgTaskStatus::getStatuses();
$applicationTypes = PbgTaskApplicationTypes::labels();
return view("quick-search.detail", compact("data", 'statusOptions', 'applicationTypes'));
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
\Log::warning("PbgTask with ID {$id} not found.");
return redirect()->route('quick-search.index')->with('error', 'Data tidak ditemukan.');
} catch (\Throwable $e) {
\Log::error("Error in QuickSearchController@show: " . $e->getMessage());
return response()->view('pages.404', [], 500); // Optional: create `resources/views/errors/500.blade.php`
}
}
}

View File

View File

@@ -0,0 +1,21 @@
document.addEventListener("DOMContentLoaded", function () {
const searchBtn = document.getElementById("searchBtn");
const searchInput = document.getElementById("searchInput");
searchBtn.addEventListener("click", function () {
const keyword = searchInput.value.trim();
if (keyword !== "") {
// Redirect to the route with query parameter
window.location.href = `/search-result?keyword=${encodeURIComponent(
keyword
)}`;
}
});
// Optional: trigger search on Enter key
searchInput.addEventListener("keydown", function (e) {
if (e.key === "Enter") {
searchBtn.click();
}
});
});

View File

@@ -0,0 +1,87 @@
import { Grid, html } from "gridjs";
class QuickSearchResult {
constructor() {
this.table = null;
const baseInput = document.getElementById("base_url_datatable");
this.datatableUrl = baseInput ? baseInput.value : "";
}
init() {
this.initDatatable();
}
initDatatable() {
const tableContainer = document.getElementById(
"datatable-quick-search-result"
);
const config = {
columns: [
"ID",
{ name: "Name" },
{ name: "Condition" },
"Registration Number",
"Document Number",
{ name: "Address" },
"Status",
"Function Type",
"Consultation Type",
{ name: "Due Date" },
{
name: "Action",
formatter: (cell) => {
return html(`
<a href="/quick-search/${cell.id}"
class="btn btn-yellow btn-sm d-inline-flex align-items-center justify-content-center"
style="white-space: nowrap; line-height: 1;">
<iconify-icon icon="mingcute:eye-2-fill" width="15" height="15" style="vertical-align: middle;"></iconify-icon>
</a>
`);
},
},
],
search: false,
pagination: {
limit: 15,
server: {
url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${
page + 1
}`,
},
},
sort: true,
server: {
url: this.datatableUrl,
then: (data) =>
data.data.map((item) => [
item.id,
item.name,
item.condition,
item.registration_number,
item.document_number,
item.address,
item.status_name,
item.function_type,
item.consultation_type,
item.due_date,
item,
]),
total: (data) => data.total,
},
};
if (this.table) {
this.table = this.table.updateConfig(config).forceRender();
} else {
tableContainer.innerHTML = "";
this.table = new Grid(config).render(tableContainer);
}
}
}
document.addEventListener("DOMContentLoaded", function () {
const app = new QuickSearchResult();
app.init();
});

View File

@@ -0,0 +1,78 @@
.gsp-body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh; // ensures full vertical centering
background: #f0f2f5;
}
.gsp-icon {
width: 180px; // slightly smaller for better mobile view
height: auto;
margin-bottom: 15px;
}
.gsp-title {
font-size: 36px;
font-weight: 700;
color: #333;
}
.gsp-input {
width: 100%;
height: 44px;
font-size: 16px;
padding: 0 20px;
border-radius: 24px;
border: 1px solid #dfe1e5;
background-color: #fff;
box-shadow: none;
transition: box-shadow 0.2s ease-in-out, border-color 0.2s;
&:focus {
border-color: transparent;
box-shadow: 0 1px 6px rgba(32, 33, 36, 0.28);
outline: none;
}
}
.gsp-btn {
height: 44px;
padding: 0 24px;
font-size: 16px;
border: none;
border-radius: 24px;
background-color: #1a73e8;
color: white;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
&:hover {
background-color: #1558d6;
}
}
@media (max-width: 576px) {
.gsp-input-wrapper {
flex-direction: column;
align-items: stretch;
}
.gsp-btn {
width: 100%;
padding: 0 20px;
}
.gsp-input {
padding: 0 20px;
font-size: 16px;
}
.gsp-title {
font-size: 28px;
}
.gsp-icon {
width: 140px;
}
}

View File

@@ -0,0 +1,90 @@
.qs-wrapper {
padding: 30px 15px;
max-width: 1200px;
margin: 0 auto;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
color: #2c3e50;
}
.qs-header {
margin-bottom: 30px;
text-align: center;
h2 {
font-size: 24px;
font-weight: 600;
color: #1a237e;
em {
font-style: normal;
color: #0d47a1;
}
}
p {
font-size: 16px;
color: #555;
}
}
.qs-table-wrapper {
background-color: #fff;
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
overflow-x: auto; // allow horizontal scroll on small screens
}
/* Grid.js overrides */
.qs-table-wrapper .gridjs {
font-size: 14px;
color: #333;
}
.qs-table-wrapper .gridjs-table {
width: 100%;
border-collapse: collapse;
}
.qs-table-wrapper .gridjs-th,
.qs-table-wrapper .gridjs-td {
padding: 12px 16px;
border: 1px solid #e0e0e0;
text-align: left;
vertical-align: middle;
}
.qs-table-wrapper .gridjs-th {
background-color: #f5f5f5;
font-weight: 600;
color: #1b1b1b;
}
.qs-table-wrapper .gridjs-tr:hover {
background-color: #f9f9f9;
}
.qs-table-wrapper .gridjs-pagination {
margin-top: 16px;
justify-content: center;
}
@media (max-width: 768px) {
.qs-header h2 {
font-size: 20px;
}
.qs-wrapper {
padding: 20px 10px;
}
.qs-table-wrapper {
padding: 15px;
}
.qs-table-wrapper .gridjs-th,
.qs-table-wrapper .gridjs-td {
padding: 10px 12px;
font-size: 13px;
}
}

View File

@@ -0,0 +1,258 @@
@extends('layouts.base', ['subtitle' => 'Quick Search'])
@section('css')
@endsection
@section('content')
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Detail Informasi Permohonan PBG</h5>
</div>
<div class="card-body">
<div class="row gy-3 gx-4">
<div class="col-md-6">
<dl class="row mb-0">
<dt class="col-sm-5">Nama Pemohon</dt>
<dd class="col-sm-7">{{ $data->name }}</dd>
<dt class="col-sm-5">Nama Pemilik</dt>
<dd class="col-sm-7">{{ $data->owner_name }}</dd>
<dt class="col-sm-5">Jenis Permohonan</dt>
<dd class="col-sm-7">{{ $applicationTypes[$data->application_type] ?? '-' }}</dd>
<dt class="col-sm-5">Kondisi</dt>
<dd class="col-sm-7">{{ $data->condition }}</dd>
<dt class="col-sm-5">Nomor Registrasi</dt>
<dd class="col-sm-7">{{ $data->registration_number }}</dd>
<dt class="col-sm-5">Nomor Dokumen</dt>
<dd class="col-sm-7">{{ $data->document_number }}</dd>
<dt class="col-sm-5">Status</dt>
<dd class="col-sm-7">{{ $statusOptions[$data->status] ?? '-' }}</dd>
</dl>
</div>
<div class="col-md-6">
<dl class="row mb-0">
<dt class="col-sm-5">Alamat</dt>
<dd class="col-sm-7">{{ $data->address }}</dd>
<dt class="col-sm-5">Status SLF</dt>
<dd class="col-sm-7">{{ $data->slf_status_name }}</dd>
<dt class="col-sm-5">Fungsi Bangunan</dt>
<dd class="col-sm-7">{{ $data->function_type }}</dd>
<dt class="col-sm-5">Jenis Konsultasi</dt>
<dd class="col-sm-7">{{ $data->consultation_type }}</dd>
<dt class="col-sm-5">Jatuh Tempo</dt>
<dd class="col-sm-7">{{ \Carbon\Carbon::parse($data->due_date)->format('d M Y') }}</dd>
<dt class="col-sm-5">Tanggal Dibuat</dt>
<dd class="col-sm-7">{{ \Carbon\Carbon::parse($data->task_created_at)->format('d M Y H:i') }}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs nav-justified">
<li class="nav-item">
<a href="#pbgTaskRetributions" data-bs-toggle="tab" aria-expanded="false"
class="nav-link active">
<span class="d-none d-sm-block">PBG Task Retributions</span>
</a>
</li>
<li class="nav-item">
<a href="#pbgTaskIntegration" data-bs-toggle="tab" aria-expanded="false" class="nav-link">
<span class="d-none d-sm-block">PBG Task Index Integrations</span>
</a>
</li>
<li class="nav-item">
<a href="#pbgTaskPrasarana" data-bs-toggle="tab" aria-expanded="false" class="nav-link">
<span class="d-none d-sm-block">PBG Task Prasarana</span>
</a>
</li>
<li class="nav-item">
<a href="#pbgTaskAssignments" data-bs-toggle="tab" aria-expanded="false" class="nav-link">
<span class="d-none d-sm-block">Penugasan</span>
</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<div class="tab-pane active" id="pbgTaskRetributions">
@if ($data->pbg_task_retributions)
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<dt>Luas Bangunan</dt>
<dd>{{$data->pbg_task_retributions->luas_bangunan}}</dd>
</div>
<div class="mb-3">
<dt>Indeks Lokalitas</dt>
<dd>{{$data->pbg_task_retributions->indeks_lokalitas}}</dd>
</div>
<div class="mb-3">
<dt>Wilayah SHST</dt>
<dd>{{$data->pbg_task_retributions->wilayah_shst}}</dd>
</div>
<div class="mb-3">
<dt>Kegiatan Name</dt>
<dd>{{$data->pbg_task_retributions->kegiatan_name}}</dd>
</div>
<div class="mb-3">
<dt>Nilai SHST</dt>
<dd>{{$data->pbg_task_retributions->nilai_shst}}</dd>
</div>
<div class="mb-3">
<dt>Indeks Integrasi</dt>
<dd>{{$data->pbg_task_retributions->indeks_terintegrasi}}</dd>
</div>
<div class="mb-3">
<dt>Indeks Bg Terbangun</dt>
<dd>{{$data->pbg_task_retributions->indeks_bg_terbangun}}</dd>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<dt>Nilai Retribusi Bangunan</dt>
<dd>{{$data->pbg_task_retributions->nilai_retribusi_bangunan}}</dd>
</div>
<div class="mb-3">
<dt>Nilai Prasarana</dt>
<dd>{{$data->pbg_task_retributions->nilai_prasarana}}</dd>
</div>
<div class="mb-3">
<dt>PBG Dokumen</dt>
<dd>{{$data->pbg_task_retributions->pbg_document}}</dd>
</div>
<div class="mb-3">
<dt>Underpayment</dt>
<dd>{{$data->pbg_task_retributions->underpayment}}</dd>
</div>
<div class="mb-3">
<dt>SKRD Amount</dt>
<dd>{{$data->pbg_task_retributions->skrd_amount}}</dd>
</div>
</div>
</div>
@else
<div class="row">
<div class="col-md-12">
Data Not Available
</div>
</div>
@endif
</div>
<div class="tab-pane" id="pbgTaskIntegration">
@if ($data->pbg_task_index_integrations)
<div class="row">
<div class="col-md-12">
<div class="mb-3">
<dt>Indeks Fungsi Bangunan</dt>
<dd>{{$data->pbg_task_index_integrations->indeks_fungsi_bangunan}}</dd>
</div>
<div class="mb-3">
<dt>Indeks Parameter Kompleksitas</dt>
<dd>{{$data->pbg_task_index_integrations->indeks_parameter_kompleksitas}}</dd>
</div>
<div class="mb-3">
<dt>Indeks Parameter Permanensi</dt>
<dd>{{$data->pbg_task_index_integrations->indeks_parameter_permanensi}}</dd>
</div>
<div class="mb-3">
<dt>Indeks Paramter Ketinggian</dt>
<dd>{{$data->pbg_task_index_integrations->indeks_parameter_ketinggian}}</dd>
</div>
<div class="mb-3">
<dt>Faktor Kepemilikan</dt>
<dd>{{$data->pbg_task_index_integrations->faktor_kepemilikan}}</dd>
</div>
<div class="mb-3">
<dt>Indeks Terintegrasi</dt>
<dd>{{$data->pbg_task_index_integrations->indeks_terintegrasi}}</dd>
</div>
<div class="mb-3">
<dt>Total</dt>
<dd>{{$data->pbg_task_index_integrations->total}}</dd>
</div>
</div>
</div>
@else
<div class="row">
<div class="col-md-12">
Data Not Available
</div>
</div>
@endif
</div>
<div class="tab-pane" id="pbgTaskPrasarana">
<div class="row d-flex flex-warp gap-3 justify-content-center">
@if ($data->pbg_task_retributions && $data->pbg_task_retributions->pbg_task_prasarana)
@foreach ($data->pbg_task_retributions->pbg_task_prasarana as $prasarana)
<div class="border p-3 rounded shadow-sm col-md-4">
<div class="mb-3">
<dt>Prasarana Type</dt>
<dd>{{$prasarana->prasarana_type}}</dd>
</div>
<div class="mb-3">
<dt>Building Type</dt>
<dd>{{$prasarana->building_type}}</dd>
</div>
<div class="mb-3">
<dt>Total</dt>
<dd>{{$prasarana->total}}</dd>
</div>
<div class="mb-3">
<dt>Quantity</dt>
<dd>{{$prasarana->quantity}}</dd>
</div>
<div class="mb-3">
<dt>Unit</dt>
<dd>{{$prasarana->unit}}</dd>
</div>
<div class="mb-3">
<dt>Index Prasarana</dt>
<dd>{{$prasarana->index_prasarana}}</dd>
</div>
<div class="mb-3">
<dt>Created At</dt>
<dd>{{$prasarana->created_at}}</dd>
</div>
</div>
@endforeach
@else
<div class="row">
<div class="col-md-12">
Data Not Available
</div>
</div>
@endif
</div>
</div>
<div class="tab-pane" id="pbgTaskAssignments">
<input type="hidden" id="uuid" value="{{ $data->uuid }}" />
<div id="table-pbg-task-assignments"></div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
@endsection

View File

@@ -0,0 +1,30 @@
@extends('layouts.base', ['subtitle' => 'Quick Search'])
@section('css')
@vite(['resources/scss/pages/quick-search/index.scss'])
@endsection
@section('body-attribuet')
class="gsp-body"
@endsection
@section('content')
<div class="container min-vh-100 d-flex justify-content-center align-items-center gsp-body">
<div class="w-100" style="max-width: 700px;">
<div class="text-center mb-4">
<img src="{{ asset('images/simbg-dputr.png') }}" alt="PBG Icon" class="img-fluid gsp-icon mb-3">
<h1 class="gsp-title">SIBEDAS PBG</h1>
</div>
<div class="d-flex flex-column flex-sm-row align-items-stretch gap-2">
<div class="flex-fill">
<input type="text" class="gsp-input" id="searchInput" placeholder="Cari..." autocomplete="off" />
</div>
<button class="gsp-btn" id="searchBtn">Cari</button>
</div>
</div>
</div>
@endsection
@section('scripts')
@vite(['resources/js/quick-search/index.js'])
@endsection

View File

@@ -0,0 +1,24 @@
@extends('layouts.base', ['subtitle' => 'Quick Search'])
@section('css')
@vite(['resources/scss/pages/quick-search/result.scss'])
@vite(['node_modules/gridjs/dist/theme/mermaid.min.css'])
@endsection
@section('content')
<input type="hidden" value="{{ $keyword }}" id="query_keyword" />
<input type="hidden" value="{{ route('quick-search-datatable', ['search' => $keyword]) }}" id="base_url_datatable" />
<div class="qs-wrapper">
<div class="qs-header">
<h2>Hasil Pencarian: <em>{{ $keyword }}</em></h2>
<p>Berikut adalah data hasil pencarian berdasarkan kata kunci yang Anda masukkan.</p>
</div>
<div class="qs-table-wrapper">
<div id="datatable-quick-search-result"></div>
</div>
</div>
@endsection
@section('scripts')
@vite(['resources/js/quick-search/result.js'])
@endsection

View File

@@ -15,6 +15,7 @@ use App\Http\Controllers\Master\UsersController;
use App\Http\Controllers\MenusController;
use App\Http\Controllers\PaymentRecapsController;
use App\Http\Controllers\PbgTaskAttachmentsController;
use App\Http\Controllers\QuickSearchController;
use App\Http\Controllers\ReportPaymentRecapsController;
use App\Http\Controllers\ReportPbgPTSPController;
use App\Http\Controllers\RequestAssignment\PbgTaskController;
@@ -34,6 +35,11 @@ use Illuminate\Support\Facades\Route;
require __DIR__ . '/auth.php';
Route::get('/search', [QuickSearchController::class, 'index'])->name('search');
Route::get('/search-result', [QuickSearchController::class, 'search_result'])->name('search-result');
Route::get('/quick-search-datatable', [QuickSearchController::class, 'quick_search_datatable'])->name('quick-search-datatable');
Route::get('/quick-search/{id}', [QuickSearchController::class, 'show'])->name('quick-search.detail');
// auth
Route::group(['middleware' => 'auth'], function(){