diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index da5c6f4..c5fff64 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -29,7 +29,8 @@ class UsersController extends Controller public function index(Request $request){ $query = User::query(); if($request->has('search') && !empty($request->get("search"))){ - $query->where('name', 'LIKE', '%'.$request->get('search').'%'); + $query->where('name', 'LIKE', '%'.$request->get('search').'%') + ->orWhere('email', 'LIKE', '%'.$request->get('search').'%'); } return UserResource::collection($query->paginate(config('app.paginate_per_page', 50))); } diff --git a/app/Http/Controllers/Dashboards/PotentialsController.php b/app/Http/Controllers/Dashboards/PotentialsController.php new file mode 100644 index 0000000..4841f0e --- /dev/null +++ b/app/Http/Controllers/Dashboards/PotentialsController.php @@ -0,0 +1,16 @@ + "Dashboard Potensi", - "url" => "dashboard.lack_of_potential", + "url" => null, "icon" => null, "parent_id" => $dashboard->id, "sort_order" => 3, @@ -236,6 +236,20 @@ class UsersRoleMenuSeeder extends Seeder "parent_id" => $chat_bedas->id, "sort_order" => 1, ], + [ + "name" => "Dalam Sistem", + "url" => "dashboard.potentials.inside_system", + "icon" => null, + "parent_id" => Menu::where('name', 'Dashboard Potensi')->first()->id, + "sort_order" => 1, + ], + [ + "name" => "Luar Sistem", + "url" => "dashboard.potentials.outside_system", + "icon" => null, + "parent_id" => Menu::where('name', 'Dashboard Potensi')->first()->id, + "sort_order" => 2, + ], ]; foreach ($children_menus as $child_menu) { @@ -261,6 +275,8 @@ class UsersRoleMenuSeeder extends Seeder $peta = Menu::where('name', 'PETA')->first(); $bigdata_resume = Menu::where('name', 'Lap Pimpinan')->first(); $chatbot = Menu::where('name', 'Chat')->first(); + $dalam_sistem = Menu::where('name', 'Dalam Sistem')->first(); + $luar_sistem = Menu::where('name', 'Luar Sistem')->first(); // Superadmin gets all menus $superadmin->menus()->sync([ @@ -289,7 +305,9 @@ class UsersRoleMenuSeeder extends Seeder $lack_of_potentials->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $spatial_plannings->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $pdam->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], - $peta->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], + // $peta->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], + $dalam_sistem->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], + $luar_sistem->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $bigdata_resume->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $chatbot->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], ]); diff --git a/resources/js/bigdata-resumes/index.js b/resources/js/bigdata-resumes/index.js index 0f476d1..af7cc3e 100644 --- a/resources/js/bigdata-resumes/index.js +++ b/resources/js/bigdata-resumes/index.js @@ -16,7 +16,7 @@ class BigdataResume { } async initEvents() { await this.initBigdataResumeTable(); - this.handleSearch(); + // this.handleSearch(); } async initBigdataResumeTable() { @@ -60,6 +60,12 @@ class BigdataResume { }, }, sort: true, + search: { + server: { + url: (prev, keyword) => `${prev}?search=${keyword}`, + }, + debounceTimeout: 1000, + }, server: { url: `${GlobalConfig.apiHost}/api/bigdata-report`, headers: { diff --git a/resources/js/business-industries/index.js b/resources/js/business-industries/index.js index bcba970..53c0ed2 100644 --- a/resources/js/business-industries/index.js +++ b/resources/js/business-industries/index.js @@ -77,6 +77,7 @@ class BusinessIndustries { server: { url: (prev, keyword) => `${prev}?search=${keyword}`, }, + debounceTimeout: 1000, }, server: { url: `${GlobalConfig.apiHost}/api/api-business-industries`, diff --git a/resources/js/customers/index.js b/resources/js/customers/index.js index 70b0b65..ac75c35 100644 --- a/resources/js/customers/index.js +++ b/resources/js/customers/index.js @@ -66,6 +66,7 @@ class Customers { server: { url: (prev, keyword) => `${prev}?search=${keyword}`, }, + debounceTimeout: 1000, }, server: { url: `${GlobalConfig.apiHost}/api/customers`, @@ -91,6 +92,8 @@ class Customers { }).render(tableContainer); } + handleSearch() {} + async handleDelete(deleteButton) { const id = deleteButton.getAttribute("data-id"); diff --git a/resources/js/dashboards/potentials/inside_system.js b/resources/js/dashboards/potentials/inside_system.js new file mode 100644 index 0000000..02576d6 --- /dev/null +++ b/resources/js/dashboards/potentials/inside_system.js @@ -0,0 +1,194 @@ +import Big from "big.js"; +import GlobalConfig, { addThousandSeparators } from "../../global-config.js"; +import InitDatePicker from "../../utils/InitDatePicker.js"; + +class DashboardPotentialInsideSystem { + async init() { + new InitDatePicker( + "#datepicker-lack-of-potential", + this.handleChangedDate.bind(this) + ).init(); + this.bigTotalLackPotential = 0; + this.totalPotensi = await this.getDataTotalPotensi("latest"); + this.totalTargetPAD = await this.getDataSettings("TARGET_PAD"); + this.allCountData = await this.getValueDashboard(); + this.reklameCount = this.allCountData.total_reklame ?? 0; + this.pdamCount = this.allCountData.total_pdam ?? 0; + this.tataRuangCount = this.allCountData.total_tata_ruang ?? 0; + + let dataReportTourism = this.allCountData.data_report; + + this.totalVilla = dataReportTourism + .filter((item) => item.kbli_title.toLowerCase() === "vila") + .reduce((sum, item) => sum + item.total_records, 0); + this.totalRestoran = dataReportTourism + .filter((item) => item.kbli_title.toLowerCase() === "restoran") + .reduce((sum, item) => sum + item.total_records, 0); + this.totalPariwisata = dataReportTourism.reduce( + (sum, item) => sum + item.total_records, + 0 + ); + + this.bigTargetPAD = new Big(this.totalTargetPAD ?? 0); + this.bigTotalPotensi = new Big(this.totalPotensi.total ?? 0); + this.bigTotalLackPotential = this.bigTargetPAD.minus( + this.bigTotalPotensi + ); + this.initChartKekuranganPotensi(); + this.initDataValueDashboard(); + } + async handleChangedDate(filterDate) { + const totalPotensi = await this.getDataTotalPotensi(filterDate); + this.bigTotalPotensi = new Big(totalPotensi.total ?? 0); + this.bigTotalLackPotential = this.bigTargetPAD.minus( + this.bigTotalPotensi + ); + + this.initChartKekuranganPotensi(); + } + async getDataTotalPotensi(filterDate) { + try { + const response = await fetch( + `${GlobalConfig.apiHost}/api/bigdata-resume?filterByDate=${filterDate}`, + { + credentials: "include", + headers: { + Authorization: `Bearer ${ + document.querySelector("meta[name='api-token']") + .content + }`, + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + console.error("Network response was not ok", response); + } + + const data = await response.json(); + return { + total: data.total_potensi.sum, + }; + } catch (error) { + console.error("Error fetching chart data:", error); + return null; + } + } + async getDataSettings(string_key) { + try { + const response = await fetch( + `${GlobalConfig.apiHost}/api/data-settings?search=${string_key}`, + { + credentials: "include", + headers: { + Authorization: `Bearer ${ + document.querySelector("meta[name='api-token']") + .content + }`, + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + console.error("Network response was not ok", response); + } + + const data = await response.json(); + return data.data[0].value; + } catch (error) { + console.error("Error fetching chart data:", error); + return 0; + } + } + async getValueDashboard() { + try { + const response = await fetch( + `${GlobalConfig.apiHost}/api/dashboard-potential-count`, + { + credentials: "include", + headers: { + Authorization: `Bearer ${ + document.querySelector("meta[name='api-token']") + .content + }`, + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + console.error("Network response was not ok", response); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Error fetching chart data:", error); + return 0; + } + } + initChartKekuranganPotensi() { + document + .querySelectorAll(".document-count.chart-lack-of-potential") + .forEach((element) => { + element.innerText = ``; + }); + document + .querySelectorAll(".document-total.chart-lack-of-potential") + .forEach((element) => { + element.innerText = `Rp.${addThousandSeparators( + this.bigTotalLackPotential.toString() + )}`; + }); + document + .querySelectorAll(".small-percentage.chart-lack-of-potential") + .forEach((element) => { + element.innerText = ``; + }); + } + initDataValueDashboard() { + document.getElementById("reklame-count").innerText = this.reklameCount; + document.getElementById("pdam-count").innerText = this.pdamCount; + document.getElementById("pbb-bangunan-count").innerText = + this.tataRuangCount; + document.getElementById("tata-ruang-count").innerText = + this.tataRuangCount; + document.getElementById("tata-ruang-usaha-count").innerText = + this.tataRuangCount; + document.getElementById("restoran-count").innerText = + this.totalRestoran; + document.getElementById("villa-count").innerText = this.totalVilla; + document.getElementById("pariwisata-count").innerText = + this.totalPariwisata; + } +} +document.addEventListener("DOMContentLoaded", async function (e) { + await new DashboardPotentialInsideSystem().init(); +}); + +function resizeDashboard() { + let targetElement = document.getElementById("lack-of-potential-wrapper"); + let dashboardElement = document.getElementById( + "lack-of-potential-fixed-container" + ); + + let targetWidth = targetElement.offsetWidth; + let dashboardWidth = 1400; + + let scaleFactor = (targetWidth / dashboardWidth).toFixed(2); + + // Prevent scaling beyond 1 (100%) to avoid overflow + scaleFactor = Math.min(scaleFactor, 1); + + dashboardElement.style.transformOrigin = "left top"; + dashboardElement.style.transition = "transform 0.2s ease-in-out"; + dashboardElement.style.transform = `scale(${scaleFactor})`; + + // Ensure horizontal scrolling is allowed if necessary + document.body.style.overflowX = "auto"; +} + +window.addEventListener("load", resizeDashboard); +window.addEventListener("resize", resizeDashboard); diff --git a/resources/js/dashboards/potentials/outside_system.js b/resources/js/dashboards/potentials/outside_system.js new file mode 100644 index 0000000..8b4fd38 --- /dev/null +++ b/resources/js/dashboards/potentials/outside_system.js @@ -0,0 +1,121 @@ +import InitDatePicker from "../../utils/InitDatePicker.js"; +import GlobalConfig, { addThousandSeparators } from "../../global-config.js"; + +class DashboardPotentialOutsideSystem { + async init() { + new InitDatePicker( + "#datepicker-outside-system", + this.handleChangedDate.bind(this) + ).init(); + this.bigTotalLackPotential = 0; + this.dataResume = await this.getBigDataResume("latest"); + console.log(this.dataResume); + this.initChartNonBusiness(); + this.initChartBusiness(); + } + async handleChangedDate(filterDate) { + this.dataResume = await this.getBigDataResume(filterDate); + this.initChartNonBusiness(); + this.initChartBusiness(); + } + async getBigDataResume(filterDate) { + try { + const response = await fetch( + `${GlobalConfig.apiHost}/api/bigdata-resume?filterByDate=${filterDate}`, + { + credentials: "include", + headers: { + Authorization: `Bearer ${ + document.querySelector("meta[name='api-token']") + .content + }`, + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + console.error("Network response was not ok", response); + } + + return await response.json(); + } catch (error) { + console.error("Error fetching chart data:", error); + return null; + } + } + + initChartNonBusiness() { + const nonBusinessDoc = this.dataResume?.non_business_document ?? {}; + + document + .querySelectorAll(".document-count.outside-system-non-business") + .forEach((element) => { + element.innerText = `${nonBusinessDoc.count ?? 0}`; + }); + + document + .querySelectorAll(".document-total.outside-system-non-business") + .forEach((element) => { + element.innerText = `Rp.${addThousandSeparators( + (nonBusinessDoc.sum ?? 0).toString() + )}`; + }); + + document + .querySelectorAll(".small-percentage.outside-system-non-business") + .forEach((element) => { + element.innerText = `${nonBusinessDoc.percentage ?? 0}%`; + }); + } + initChartBusiness() { + const businessDoc = this.dataResume?.business_document ?? {}; + + document + .querySelectorAll(".document-count.outside-system-business") + .forEach((element) => { + element.innerText = `${businessDoc.count ?? 0}`; + }); + + document + .querySelectorAll(".document-total.outside-system-business") + .forEach((element) => { + element.innerText = `Rp.${addThousandSeparators( + (businessDoc.sum ?? 0).toString() + )}`; + }); + + document + .querySelectorAll(".small-percentage.outside-system-business") + .forEach((element) => { + element.innerText = `${businessDoc.percentage ?? 0}%`; + }); + } +} +document.addEventListener("DOMContentLoaded", async function (e) { + await new DashboardPotentialOutsideSystem().init(); +}); +function resizeDashboard() { + let targetElement = document.getElementById("outside-system-wrapper"); + let dashboardElement = document.getElementById( + "outside-system-fixed-container" + ); + + let targetWidth = targetElement.offsetWidth; + let dashboardWidth = 1400; + + let scaleFactor = (targetWidth / dashboardWidth).toFixed(2); + + // Prevent scaling beyond 1 (100%) to avoid overflow + scaleFactor = Math.min(scaleFactor, 1); + + dashboardElement.style.transformOrigin = "left top"; + dashboardElement.style.transition = "transform 0.2s ease-in-out"; + dashboardElement.style.transform = `scale(${scaleFactor})`; + + // Ensure horizontal scrolling is allowed if necessary + document.body.style.overflowX = "auto"; +} + +window.addEventListener("load", resizeDashboard); +window.addEventListener("resize", resizeDashboard); diff --git a/resources/js/data-settings/index.js b/resources/js/data-settings/index.js index 7ae9ef7..b4344e5 100644 --- a/resources/js/data-settings/index.js +++ b/resources/js/data-settings/index.js @@ -67,6 +67,7 @@ class DataSettings { server: { url: (prev, keyword) => `${prev}?search=${keyword}`, }, + debounceTimeout: 1000, }, server: { url: `${GlobalConfig.apiHost}/api/data-settings`, diff --git a/resources/js/master/users/users.js b/resources/js/master/users/users.js index 5506e06..9f9a903 100644 --- a/resources/js/master/users/users.js +++ b/resources/js/master/users/users.js @@ -44,6 +44,7 @@ class UsersTable { server: { url: (prev, keyword) => `${prev}?search=${keyword}`, }, + debounceTimeout: 1000, }, server: { url: `${GlobalConfig.apiHost}/api/users`, diff --git a/resources/js/menus/index.js b/resources/js/menus/index.js index 92109c5..7bf5ff7 100644 --- a/resources/js/menus/index.js +++ b/resources/js/menus/index.js @@ -28,35 +28,35 @@ class Menus { initTableMenus() { let tableContainer = document.getElementById("table-menus"); - if (this.table) { - // If table exists, update its data instead of recreating - this.table - .updateConfig({ - server: { - url: `${GlobalConfig.apiHost}/api/menus`, - credentials: "include", - headers: { - Authorization: `Bearer ${document - .querySelector('meta[name="api-token"]') - .getAttribute("content")}`, - "Content-Type": "application/json", - }, - then: (data) => - data.data.map((item) => [ - item.id, - item.name, - item.url, - item.icon, - item.parent_id, - item.sort_order, - item.id, - ]), - total: (data) => data.total, - }, - }) - .forceRender(); - return; - } + // if (this.table) { + // // If table exists, update its data instead of recreating + // this.table + // .updateConfig({ + // server: { + // url: `${GlobalConfig.apiHost}/api/menus`, + // credentials: "include", + // headers: { + // Authorization: `Bearer ${document + // .querySelector('meta[name="api-token"]') + // .getAttribute("content")}`, + // "Content-Type": "application/json", + // }, + // then: (data) => + // data.data.map((item) => [ + // item.id, + // item.name, + // item.url, + // item.icon, + // item.parent_id, + // item.sort_order, + // item.id, + // ]), + // total: (data) => data.total, + // }, + // }) + // .forceRender(); + // return; + // } this.table = new Grid({ columns: [ @@ -96,6 +96,7 @@ class Menus { server: { url: (prev, keyword) => `${prev}?search=${keyword}`, }, + debounceTimeout: 1000, }, server: { url: `${GlobalConfig.apiHost}/api/menus`, diff --git a/resources/js/pbg-task/index.js b/resources/js/pbg-task/index.js index 9b1d228..031812a 100644 --- a/resources/js/pbg-task/index.js +++ b/resources/js/pbg-task/index.js @@ -36,6 +36,7 @@ class PbgTasks { server: { url: (prev, keyword) => `${prev}?search=${keyword}`, }, + debounceTimeout: 1000, }, pagination: { limit: 15, diff --git a/resources/js/roles/index.js b/resources/js/roles/index.js index c4ab98d..68d1235 100644 --- a/resources/js/roles/index.js +++ b/resources/js/roles/index.js @@ -65,6 +65,7 @@ class Roles { server: { url: (prev, keyword) => `${prev}?search=${keyword}`, }, + debounceTimeout: 1000, }, server: { url: `${GlobalConfig.apiHost}/api/roles`, diff --git a/resources/js/table-generator.js b/resources/js/table-generator.js index 45da245..6314113 100644 --- a/resources/js/table-generator.js +++ b/resources/js/table-generator.js @@ -18,12 +18,15 @@ class GeneralTable { server: { url: (prev, keyword) => `${prev}?search=${keyword}`, }, + debounceTimeout: 1000, }, pagination: this.options.pagination || { limit: 15, server: { url: (prev, page) => - `${prev}${prev.includes("?") ? "&" : "?"}page=${page + 1}`, + `${prev}${prev.includes("?") ? "&" : "?"}page=${ + page + 1 + }`, }, }, sort: this.options.sort || true, @@ -48,22 +51,29 @@ class GeneralTable { processData(data) { return data.data.map((item) => { return this.columns.map((column) => { - return item[column] || ''; + return item[column] || ""; }); }); } handleActions() { document.addEventListener("click", (event) => { - if (event.target && event.target.classList.contains('btn-edit')) { + if (event.target && event.target.classList.contains("btn-edit")) { this.handleEdit(event); - } - else if (event.target && event.target.classList.contains('btn-delete')) { + } else if ( + event.target && + event.target.classList.contains("btn-delete") + ) { this.handleDelete(event); - } - else if (event.target && event.target.classList.contains('btn-create')) { + } else if ( + event.target && + event.target.classList.contains("btn-create") + ) { this.handleCreate(event); - } else if (event.target && event.target.classList.contains('btn-bulk-create')) { + } else if ( + event.target && + event.target.classList.contains("btn-bulk-create") + ) { this.handleBulkCreate(event); } }); @@ -72,28 +82,28 @@ class GeneralTable { // Fungsi untuk menangani create handleCreate(event) { // Menggunakan model dan ID untuk membangun URL dinamis - const model = event.target.getAttribute('data-model'); // Mengambil model dari data-model + const model = event.target.getAttribute("data-model"); // Mengambil model dari data-model window.location.href = `${this.baseUrl}/${model}/create`; } handleBulkCreate(event) { // Menggunakan model dan ID untuk membangun URL dinamis - const model = event.target.getAttribute('data-model'); + const model = event.target.getAttribute("data-model"); window.location.href = `${this.baseUrl}/${model}/bulk-create`; } // Fungsi untuk menangani edit handleEdit(event) { - const id = event.target.getAttribute('data-id'); - const model = event.target.getAttribute('data-model'); // Mengambil model dari data-model - console.log('Editing record with ID:', id); + const id = event.target.getAttribute("data-id"); + const model = event.target.getAttribute("data-model"); // Mengambil model dari data-model + console.log("Editing record with ID:", id); // Menggunakan model dan ID untuk membangun URL dinamis window.location.href = `${this.baseUrl}/${model}/${id}/edit`; } // Fungsi untuk menangani delete handleDelete(event) { - const id = event.target.getAttribute('data-id'); + const id = event.target.getAttribute("data-id"); console.log(id); // if (confirm("Are you sure you want to delete this item?")) { // this.deleteRecord(id); @@ -105,7 +115,7 @@ class GeneralTable { showCancelButton: true, confirmButtonColor: "#d33", cancelButtonColor: "#3085d6", - confirmButtonText: "Yes, delete it!" + confirmButtonText: "Yes, delete it!", }).then((result) => { if (result.isConfirmed) { this.deleteRecord(id); @@ -114,8 +124,8 @@ class GeneralTable { text: "Your record has been deleted.", icon: "success", showConfirmButton: false, // Menghilangkan tombol OK - timer: 2000 // Menutup otomatis dalam 2 detik (opsional) - }); + timer: 2000, // Menutup otomatis dalam 2 detik (opsional) + }); } }); } @@ -123,8 +133,9 @@ class GeneralTable { async deleteRecord(id) { try { console.log(id); - const response = await fetch(`${this.apiUrl}/${id}`, { // Menambahkan model dalam URL - method: 'DELETE', + const response = await fetch(`${this.apiUrl}/${id}`, { + // Menambahkan model dalam URL + method: "DELETE", headers: this.options.headers || { Authorization: `Bearer ${document .querySelector('meta[name="api-token"]') @@ -136,10 +147,14 @@ class GeneralTable { location.reload(); } else { const data = await response.json(); - showErrorAlert(`Failed to delete record: ${data.message || "Unknown error"}`); + showErrorAlert( + `Failed to delete record: ${ + data.message || "Unknown error" + }` + ); } } catch (error) { - console.error('Error deleting data:', error); + console.error("Error deleting data:", error); showErrorAlert("Error deleting data. Please try again."); } } @@ -148,7 +163,7 @@ class GeneralTable { // Fungsi untuk menampilkan alert function showErrorAlert(message) { const alertContainer = document.getElementById("alert-container"); - + alertContainer.innerHTML = `