diff --git a/app/Http/Controllers/QuickSearchController.php b/app/Http/Controllers/QuickSearchController.php index 7fafc97..fb2b5ad 100644 --- a/app/Http/Controllers/QuickSearchController.php +++ b/app/Http/Controllers/QuickSearchController.php @@ -17,6 +17,10 @@ class QuickSearchController extends Controller return view("quick-search.index"); } + public function public_search(){ + return view("public-search.index"); + } + public function search_result(Request $request){ $keyword = $request->get("keyword"); @@ -60,6 +64,42 @@ class QuickSearchController extends Controller } } + public function public_search_datatable(Request $request) + { + try { + // Gunakan subquery untuk performa yang lebih baik dan menghindari duplikasi + $query = PbgTask::select([ + 'pbg_task.*', + DB::raw('(SELECT name_building FROM pbg_task_details WHERE pbg_task_details.pbg_task_uid = pbg_task.uuid LIMIT 1) as name_building'), + DB::raw('(SELECT nilai_retribusi_bangunan FROM pbg_task_retributions WHERE pbg_task_retributions.pbg_task_uid = pbg_task.uuid LIMIT 1) as nilai_retribusi_bangunan') + ]) + ->orderBy('pbg_task.id', 'desc'); + + if ($request->filled('search')) { + $search = trim($request->get('search')); + $query->where(function ($q) use ($search) { + $q->where('pbg_task.registration_number', 'LIKE', "%$search%") + ->orWhere('pbg_task.name', 'LIKE', "%$search%") + ->orWhere('pbg_task.owner_name', 'LIKE', "%$search%") + ->orWhereExists(function ($subQuery) use ($search) { + $subQuery->select(DB::raw(1)) + ->from('pbg_task_details') + ->whereColumn('pbg_task_details.pbg_task_uid', 'pbg_task.uuid') + ->where('pbg_task_details.name_building', '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 { diff --git a/resources/js/public-search/index.js b/resources/js/public-search/index.js new file mode 100644 index 0000000..5499c62 --- /dev/null +++ b/resources/js/public-search/index.js @@ -0,0 +1,130 @@ +import { Grid, html } from "gridjs"; +import { addThousandSeparators } from "../global-config"; + +class PublicSearch { + constructor() { + this.table = null; + const baseInput = document.getElementById("base_url_datatable"); + this.baseUrl = baseInput ? baseInput.value.split("?")[0] : ""; + this.keywordInput = document.getElementById("search_input"); + this.searchButton = document.getElementById("search_button"); + + this.datatableUrl = this.buildUrl(this.keywordInput.value); + } + + init() { + this.bindSearchButton(); + this.initDatatable(); + } + + bindSearchButton() { + const handleSearch = () => { + const newKeyword = this.keywordInput.value.trim(); + + // 1. Update datatable URL and reload + this.datatableUrl = this.buildUrl(newKeyword); + this.initDatatable(); + + // 2. Update URL query string (tanpa reload page) + const newUrl = `${window.location.pathname}${ + newKeyword ? `?keyword=${encodeURIComponent(newKeyword)}` : "" + }`; + window.history.pushState({ path: newUrl }, "", newUrl); + + // 3. Update visible keyword text di {{ $keyword }}> + const keywordDisplay = document.querySelector(".qs-header em"); + if (keywordDisplay) { + keywordDisplay.textContent = newKeyword || "-"; + } + }; + + this.searchButton.addEventListener("click", handleSearch); + + this.keywordInput.addEventListener("keydown", (event) => { + if (event.key === "Enter") { + event.preventDefault(); + handleSearch(); + } + }); + } + + buildUrl(keyword) { + const url = new URL(this.baseUrl, window.location.origin); + + if (keyword && keyword.trim() !== "") { + url.searchParams.set("search", keyword); + } else { + url.searchParams.delete("search"); // pastikan tidak ada search param + } + + return url.toString(); + } + + initDatatable() { + const tableContainer = document.getElementById( + "datatable-public-search" + ); + + const config = { + columns: [ + "ID", + { name: "Nama Pemohon" }, + { name: "Nama Pemilik" }, + { name: "Kondisi" }, + "Nomor Registrasi", + "Status", + "Jenis Fungsi", + { name: "Nama Bangunan" }, + "Jenis Konsultasi", + { name: "Tanggal Jatuh Tempo" }, + { name: "Retribusi" }, + ], + 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.owner_name, + item.condition, + item.registration_number, + item.status_name, + item.function_type, + item.name_building, + item.consultation_type, + item.due_date, + addThousandSeparators(item.nilai_retribusi_bangunan), + ]), + total: (data) => data.total, + }, + }; + + if (this.table) { + this.table + .updateConfig({ + ...config, + server: { ...config.server, url: this.datatableUrl }, + }) + .forceRender(); + } else { + tableContainer.innerHTML = ""; + this.table = new Grid(config).render(tableContainer); + } + } +} + +document.addEventListener("DOMContentLoaded", function () { + const app = new PublicSearch(); + app.init(); +}); diff --git a/resources/scss/pages/public-search/index.scss b/resources/scss/pages/public-search/index.scss new file mode 100644 index 0000000..645f686 --- /dev/null +++ b/resources/scss/pages/public-search/index.scss @@ -0,0 +1,149 @@ +.qs-wrapper { + width: 100%; + margin: 0 auto; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + color: #2c3e50; +} + +.qs-toolbar { + border-bottom: 1px solid #e0e0e0; + margin-bottom: 1.5rem; +} + +.qs-search-form { + width: 100%; + max-width: 1000px; // biar kotak ga terlalu panjang di layar besar + margin: 0 auto; // center + + position: relative; + + .gsp-input { + width: 100%; + height: 48px; + font-size: 16px; + padding: 0 48px 0 48px; // kasih space kiri buat icon + border-radius: 999px; // pill shape + 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; + } + } + + // ikon search di kiri input + &::before { + content: "🔍"; + position: absolute; + left: 18px; + top: 50%; + transform: translateY(-50%); + font-size: 18px; + color: #5f6368; + pointer-events: none; + } + + .gsp-btn { + margin-left: 12px; + height: 44px; + padding: 0 24px; + font-size: 14px; + border: none; + border-radius: 999px; + background-color: #007c61; + color: #ffffff; + cursor: pointer; + transition: background-color 0.2s ease-in-out; + + &:hover { + background-color: #36a852; + } + } +} + +.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; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + overflow-x: auto; // allow horizontal scroll on small screens + padding: 30px 20px; // 🔑 kasih jarak kanan, kiri, atas, bawah +} + +/* 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; + } +} diff --git a/resources/views/public-search/index.blade.php b/resources/views/public-search/index.blade.php new file mode 100644 index 0000000..e4649bb --- /dev/null +++ b/resources/views/public-search/index.blade.php @@ -0,0 +1,39 @@ +@extends('layouts.base', ['subtitle' => 'Quick Search']) + +@section('css') +@vite(['resources/scss/pages/public-search/index.scss']) +@vite(['node_modules/gridjs/dist/theme/mermaid.min.css']) +@endsection + +@section('content') + + +
+
+ +
+ + +
+
+ +
+

Hasil Pencarian

+

Berikut adalah data hasil pencarian berdasarkan kata kunci yang Anda masukkan.

+
+ +
+ +
+
+@endsection + +@section('scripts') +@vite(['resources/js/public-search/index.js']) +@endsection diff --git a/routes/web.php b/routes/web.php index 49aa3a9..4f6d8d4 100644 --- a/routes/web.php +++ b/routes/web.php @@ -38,8 +38,10 @@ use Illuminate\Support\Facades\Route; require __DIR__ . '/auth.php'; Route::get('/search', [QuickSearchController::class, 'index'])->name('search'); +Route::get('/public-search', [QuickSearchController::class, 'public_search'])->name('public-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('/public-search-datatable', [QuickSearchController::class, 'public_search_datatable'])->name('public-search-datatable'); Route::get('/quick-search/{id}', [QuickSearchController::class, 'show'])->name('quick-search.detail'); Route::get('/quick-search/{uuid}/task-assignments', [QuickSearchController::class, 'task_assignments'])->name('api.quick-search-task-assignments'); diff --git a/vite.config.js b/vite.config.js index 01103cc..16575f3 100644 --- a/vite.config.js +++ b/vite.config.js @@ -53,6 +53,7 @@ export default defineConfig(({ mode }) => { "resources/scss/pages/quick-search/detail.scss", "resources/scss/pages/quick-search/index.scss", "resources/scss/pages/quick-search/result.scss", + "resources/scss/pages/public-search/index.scss", "resources/scss/pages/pbg-task/show.scss", "node_modules/quill/dist/quill.snow.css", @@ -148,6 +149,8 @@ export default defineConfig(({ mode }) => { "resources/js/quick-search/index.js", "resources/js/quick-search/result.js", "resources/js/quick-search/detail.js", + // public-search + "resources/js/public-search/index.js", // growth-report "resources/js/report/growth-report/index.js", // dummy