From 3bfcaddba4c4959de012257e4584f43b42672658 Mon Sep 17 00:00:00 2001 From: arifal Date: Mon, 3 Mar 2025 22:55:57 +0700 Subject: [PATCH 01/15] fix queue execute scraping syncronize simbg add new column to resume --- app/Console/Commands/ExecuteScraping.php | 6 +- .../Api/LackOfPotentialController.php | 5 +- .../Controllers/Api/ScrapingController.php | 5 +- .../Settings/SyncronizeController.php | 4 +- app/Jobs/SyncronizeSIMBG.php | 27 +++ app/Models/BigdataResume.php | 32 ++- app/Providers/AppServiceProvider.php | 6 +- app/Services/GoogleSheetService.php | 1 + app/Services/ServiceSIMBG.php | 228 +++++++++++++++++- ...851_add_some_column_in_bigdata_resumes.php | 38 +++ resources/js/bigdata-resumes/index.js | 24 +- resources/js/dashboards/lack-of-potential.js | 22 ++ .../js/settings/syncronize/syncronize.js | 157 ++++++------ .../dashboards/lack_of_potential.blade.php | 33 ++- resources/views/layouts/vertical.blade.php | 57 +++-- .../views/settings/syncronize/index.blade.php | 8 +- 16 files changed, 503 insertions(+), 150 deletions(-) create mode 100644 app/Jobs/SyncronizeSIMBG.php create mode 100644 database/migrations/2025_03_03_174851_add_some_column_in_bigdata_resumes.php diff --git a/app/Console/Commands/ExecuteScraping.php b/app/Console/Commands/ExecuteScraping.php index 9cee57b..48159df 100644 --- a/app/Console/Commands/ExecuteScraping.php +++ b/app/Console/Commands/ExecuteScraping.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Jobs\SyncronizeSIMBG; use App\Services\ServiceSIMBG; use Illuminate\Console\Command; use \Illuminate\Support\Facades\Log; @@ -28,13 +29,12 @@ class ExecuteScraping extends Command private $service_simbg; - public function __construct(ServiceSIMBG $service_simbg){ - $this->service_simbg = $service_simbg; + public function __construct(){ parent::__construct(); } public function handle() { + SyncronizeSIMBG::dispatch(); Log::info("running scheduler daily scraping"); - $this->service_simbg->syncTaskList(); } } diff --git a/app/Http/Controllers/Api/LackOfPotentialController.php b/app/Http/Controllers/Api/LackOfPotentialController.php index d681057..145f8c8 100644 --- a/app/Http/Controllers/Api/LackOfPotentialController.php +++ b/app/Http/Controllers/Api/LackOfPotentialController.php @@ -7,6 +7,7 @@ use App\Models\Advertisement; use App\Models\Customer; use App\Models\SpatialPlanning; use Illuminate\Http\Request; +use App\Models\TourismBasedKBLI; class LackOfPotentialController extends Controller { @@ -16,11 +17,13 @@ class LackOfPotentialController extends Controller $total_reklame = Advertisement::count(); $total_pdam = Customer::count(); $total_tata_ruang = SpatialPlanning::count(); + $data_report_tourism = TourismBasedKBLI::all(); return response()->json([ 'total_reklame' => $total_reklame, 'total_pdam' => $total_pdam, - 'total_tata_ruang' => $total_tata_ruang + 'total_tata_ruang' => $total_tata_ruang, + 'data_report' => $data_report_tourism, ], 200); }catch(\Exception $e){ return response()->json([ diff --git a/app/Http/Controllers/Api/ScrapingController.php b/app/Http/Controllers/Api/ScrapingController.php index ef8b7ff..6a4819c 100644 --- a/app/Http/Controllers/Api/ScrapingController.php +++ b/app/Http/Controllers/Api/ScrapingController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api; use App\Enums\ImportDatasourceStatus; use App\Http\Controllers\Controller; +use App\Jobs\SyncronizeSIMBG; use App\Models\ImportDatasource; use App\Traits\GlobalApiResponse; use Illuminate\Support\Facades\Artisan; @@ -23,8 +24,8 @@ class ScrapingController extends Controller } // run service artisan command - Artisan::call("app:execute-scraping"); - return $this->resSuccess("Success execute scraping service please wait"); + SyncronizeSIMBG::dispatch(); + return $this->resSuccess(["message" => "Success execute scraping service on background, check status for more"]); } /** diff --git a/app/Http/Controllers/Settings/SyncronizeController.php b/app/Http/Controllers/Settings/SyncronizeController.php index 65548df..cbdba15 100644 --- a/app/Http/Controllers/Settings/SyncronizeController.php +++ b/app/Http/Controllers/Settings/SyncronizeController.php @@ -17,12 +17,12 @@ class SyncronizeController extends Controller } public function syncPbgTask(){ - $res = $this->service_simbg->syncTaskList(); + $res = $this->service_simbg->syncTaskPBG(); return $res; } public function syncronizeTask(Request $request){ - $res = $this->service_simbg->syncTaskList(); + $res = $this->service_simbg->syncTaskPBG(); return redirect()->back()->with('success', 'Processing completed successfully'); } diff --git a/app/Jobs/SyncronizeSIMBG.php b/app/Jobs/SyncronizeSIMBG.php new file mode 100644 index 0000000..6319d48 --- /dev/null +++ b/app/Jobs/SyncronizeSIMBG.php @@ -0,0 +1,27 @@ +syncTaskPBG(); + } +} diff --git a/app/Models/BigdataResume.php b/app/Models/BigdataResume.php index 081ce19..68b0119 100644 --- a/app/Models/BigdataResume.php +++ b/app/Models/BigdataResume.php @@ -23,7 +23,13 @@ class BigdataResume extends Model 'non_business_sum', 'spatial_count', 'spatial_sum', - 'year' + 'year', + 'waiting_click_dpmptsp_count', + 'waiting_click_dpmptsp_sum', + 'issuance_realization_pbg_count', + 'issuance_realization_pbg_sum', + 'process_in_technical_office_count', + 'process_in_technical_office_sum', ]; public function importDatasource() @@ -31,7 +37,7 @@ class BigdataResume extends Model return $this->belongsTo(ImportDatasource::class, 'import_datasource_id'); } - public static function generateResumeData($import_datasource_id, $year){ + public static function generateResumeData($import_datasource_id, $year, $data_setting){ $stats = PbgTask::with(['googleSheet', 'pbg_task_retributions']) ->leftJoin('pbg_task_retributions as ptr', 'pbg_task.uuid', '=', 'ptr.pbg_task_uid') ->leftJoin('pbg_task_google_sheet as ptgs', 'pbg_task.registration_number', '=', 'ptgs.no_registrasi') @@ -82,9 +88,15 @@ class BigdataResume extends Model $potention_total = $query_potention->total_retribution ?? 0; $query_spatial_plannings = once(function () use ($year) { - $query = PbgTask::join('spatial_plannings as sp', 'pbg_task.document_number', '=', 'sp.number') - ->join('pbg_task_retributions as ptr', 'ptr.pbg_task_uid', '=', 'pbg_task.uuid') - ->selectRaw('COUNT(DISTINCT pbg_task.id) as task_count, SUM(ptr.nilai_retribusi_bangunan) as total_retribution'); + $query = PbgTask::leftJoin('spatial_plannings as sp', 'pbg_task.document_number', '=', 'sp.number') + ->leftJoin('pbg_task_retributions as ptr', 'ptr.pbg_task_uid', '=', 'pbg_task.uuid') + ->selectRaw(' + CASE + WHEN COUNT(DISTINCT sp.id) > 0 THEN COUNT(DISTINCT sp.id) + ELSE (SELECT COUNT(*) FROM spatial_plannings) + END as task_count, + SUM(CASE WHEN sp.id IS NOT NULL AND ptr.id IS NOT NULL THEN ptr.nilai_retribusi_bangunan ELSE 0 END) as total_retribution + '); if ($year !== 'all') { $query->whereYear('pbg_task.task_created_at', (int) $year); @@ -94,7 +106,7 @@ class BigdataResume extends Model }); $spatial_planning_count = $query_spatial_plannings->task_count ?? 0; - $spatial_planning_total = $query_spatial_plannings->total_retribution ?? 0; + $spatial_planning_total = $query_spatial_plannings->total_retribution; $potention_count -= $spatial_planning_count; $potention_total -= $spatial_planning_total; @@ -113,7 +125,13 @@ class BigdataResume extends Model 'business_sum' => $business_total ?? 0.00, 'non_business_count' => $non_business_count ?? 0, 'non_business_sum' => $non_business_total ?? 0.00, - 'year' => $year + 'year' => $year, + 'waiting_click_dpmptsp_count' => $data_setting['MENUNGGU_KLIK_DPMPTSP_COUNT'] ?? 0, + 'waiting_click_dpmptsp_sum' => $data_setting['MENUNGGU_KLIK_DPMPTSP_SUM'] ?? 0.00, + 'issuance_realization_pbg_count' => $data_setting['REALISASI_TERBIT_PBG_COUNT'] ?? 0, + 'issuance_realization_pbg_sum' => $data_setting['REALISASI_TERBIT_PBG_SUM'] ?? 0.00, + 'process_in_technical_office_count' => $data_setting['PROSES_DINAS_TEKNIS_COUNT'] ?? 0, + 'process_in_technical_office_sum' => $data_setting['PROSES_DINAS_TEKNIS_SUM'] ??0.00, ]); } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 714c4b6..68ad581 100755 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -9,6 +9,8 @@ use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider; use Carbon\Carbon; +use App\Services\ServiceSIMBG; +use App\Services\GoogleSheetService; class AppServiceProvider extends ServiceProvider { @@ -17,7 +19,9 @@ class AppServiceProvider extends ServiceProvider */ public function register(): void { - // + $this->app->singleton(ServiceSIMBG::class, function ($app) { + return new ServiceSIMBG($app->make(GoogleSheetService::class)); + }); } /** diff --git a/app/Services/GoogleSheetService.php b/app/Services/GoogleSheetService.php index ad60527..f8483ff 100644 --- a/app/Services/GoogleSheetService.php +++ b/app/Services/GoogleSheetService.php @@ -13,6 +13,7 @@ class GoogleSheetService protected $client; protected $service; protected $spreadsheetID; + protected $service_sheets; public function __construct() { $this->client = new Google_Client(); diff --git a/app/Services/ServiceSIMBG.php b/app/Services/ServiceSIMBG.php index 1f81c22..5419daa 100644 --- a/app/Services/ServiceSIMBG.php +++ b/app/Services/ServiceSIMBG.php @@ -15,6 +15,9 @@ use App\Traits\GlobalApiResponse; use Illuminate\Support\Facades\Log; use Carbon\Carbon; use App\Services\ServiceClient; +use App\Services\GoogleSheetService; +use App\Models\DataSetting; +use App\Models\PbgTaskGoogleSheet; class ServiceSIMBG { @@ -24,10 +27,11 @@ class ServiceSIMBG private $simbg_host; private $fetch_per_page; private $service_client; + private $googleSheetService; /** * Create a new class instance. */ - public function __construct() + public function __construct(GoogleSheetService $googleSheetService) { $settings = GlobalSetting::whereIn('key', [ 'SIMBG_EMAIL', 'SIMBG_PASSWORD', 'SIMBG_HOST', 'FETCH_PER_PAGE' @@ -39,6 +43,7 @@ class ServiceSIMBG $this->fetch_per_page = trim((string) ($settings['FETCH_PER_PAGE'] ?? "")); $this->service_client = new ServiceClient($this->simbg_host); + $this->googleSheetService = $googleSheetService; } public function getToken(){ @@ -112,12 +117,173 @@ class ServiceSIMBG } } - public function syncTaskList() + public function syncTaskPBG() { try { $importDatasource = ImportDatasource::create([ 'status' => ImportDatasourceStatus::Processing->value, ]); + + // sync google sheet first + $totalRowCount = $this->googleSheetService->getLastRowByColumn("C"); + $sheetData = $this->googleSheetService->getSheetDataCollection($totalRowCount); + $sheet_big_data = $this->googleSheetService->get_data_by_sheet(); + $data_setting_result = []; // Initialize result storage + + $found_section = null; // Track which section is found + + foreach ($sheet_big_data as $row) { + // Check for section headers + if (in_array("•PROSES PENERBITAN:", $row)) { + $found_section = "MENUNGGU_KLIK_DPMPTSP"; + } elseif (in_array("•BERKAS AKTUAL TERVERIFIKASI DINAS TEKNIS 2024:", $row)) { + $found_section = "REALISASI_TERBIT_PBG"; + } elseif (in_array("•TERPROSES DI DPUTR: belum selesai rekomtek'", $row)) { + $found_section = "PROSES_DINAS_TEKNIS"; + } + + // If a section is found and we reach "Grand Total", save the corresponding values + if ($found_section && isset($row[0]) && trim($row[0]) === "Grand Total") { + if ($found_section === "MENUNGGU_KLIK_DPMPTSP") { + $data_setting_result["MENUNGGU_KLIK_DPMPTSP_COUNT"] = $row[2] ?? null; + $data_setting_result["MENUNGGU_KLIK_DPMPTSP_SUM"] = $row[3] ?? null; + } elseif ($found_section === "REALISASI_TERBIT_PBG") { + $data_setting_result["REALISASI_TERBIT_PBG_COUNT"] = $row[2] ?? null; + $data_setting_result["REALISASI_TERBIT_PBG_SUM"] = $row[4] ?? null; + } elseif ($found_section === "PROSES_DINAS_TEKNIS") { + $data_setting_result["PROSES_DINAS_TEKNIS_COUNT"] = $row[2] ?? null; + $data_setting_result["PROSES_DINAS_TEKNIS_SUM"] = $row[3] ?? null; + } + + // Reset section tracking after capturing "Grand Total" + $found_section = null; + } + } + + foreach ($data_setting_result as $key => $value) { + DataSetting::updateOrInsert( + ["key" => $key], // Find by key + ["value" => $value] // Update or insert value + ); + } + $mapToUpsert = []; + $count = 0; + + foreach($sheetData as $data){ + $mapToUpsert[] = + [ + 'no_registrasi' => $data['no__registrasi'] ?? null, + 'jenis_konsultasi' => $data['jenis_konsultasi'] ?? null, + 'fungsi_bg' => $data['fungsi_bg'] ?? null, + 'tgl_permohonan' => $this->convertToDate($data['tgl_permohonan']), + 'status_verifikasi' => $data['status_verifikasi'] ?? null, + 'status_permohonan' => $this->convertToDate($data['status_permohonan']), + 'alamat_pemilik' => $data['alamat_pemilik'] ?? null, + 'no_hp' => $data['no__hp'] ?? null, + 'email' => $data['e_mail'] ?? null, + 'tanggal_catatan' => $this->convertToDate($data['tanggal_catatan']), + 'catatan_kekurangan_dokumen' => $data['catatan_kekurangan_dokumen'] ?? null, + 'gambar' => $data['gambar'] ?? null, + 'krk_kkpr' => $data['krk_kkpr'] ?? null, + 'no_krk' => $data['no__krk'] ?? null, + 'lh' => $data['lh'] ?? null, + 'ska' => $data['ska'] ?? null, + 'keterangan' => $data['keterangan'] ?? null, + 'helpdesk' => $data['helpdesk'] ?? null, + 'pj' => $data['pj'] ?? null, + 'kepemilikan' => $data['kepemilikan'] ?? null, + 'potensi_taru' => $data['potensi_taru'] ?? null, + 'validasi_dinas' => $data['validasi_dinas'] ?? null, + 'kategori_retribusi' => $data['kategori_retribusi'] ?? null, + 'no_urut_ba_tpt' => $data['no__urut_ba_tpt__2024_0001_'] ?? null, + 'tanggal_ba_tpt' => $this->convertToDate($data['tanggal_ba_tpt']), + 'no_urut_ba_tpa' => $data['no__urut_ba_tpa'] ?? null, + 'tanggal_ba_tpa' => $this->convertToDate($data['tanggal_ba_tpa']), + 'no_urut_skrd' => $data['no__urut_skrd__2024_0001_'] ?? null, + 'tanggal_skrd' => $this->convertToDate($data['tanggal_skrd']), + 'ptsp' => $data['ptsp'] ?? null, + 'selesai_terbit' => $data['selesai_terbit'] ?? null, + 'tanggal_pembayaran' => $this->convertToDate($data['tanggal_pembayaran__yyyy_mm_dd_']), + 'format_sts' => $data['format_sts'] ?? null, + 'tahun_terbit' => (int) $data['tahun_terbit'] ?? null, + 'tahun_berjalan' => (int) $data['tahun_berjalan'] ?? null, + 'kelurahan' => $data['kelurahan'] ?? null, + 'kecamatan' => $data['kecamatan'] ?? null, + 'lb' => $this->convertToDecimal($data['lb']) ?? null, + 'tb' => $this->convertToDecimal($data['tb']) ?? null, + 'jlb' => (int) $data['jlb'] ?? null, + 'unit' => (int) $data['unit'] ?? null, + 'usulan_retribusi' => (int) $data['usulan_retribusi'] ?? null, + 'nilai_retribusi_keseluruhan_simbg' => $this->convertToDecimal($data['nilai_retribusi_keseluruhan__simbg_']) ?? null, + 'nilai_retribusi_keseluruhan_pad' => $this->convertToDecimal($data['nilai_retribusi_keseluruhan__pad_']) ?? null, + 'denda' => $this->convertToDecimal($data['denda']) ?? null, + 'latitude' => $data['latitude'] ?? null, + 'longitude' => $data['longitude'] ?? null, + 'nik_nib' => $data['nik_nib'] ?? null, + 'dok_tanah' => $data['dok__tanah'] ?? null, + 'temuan' => $data['temuan'] ?? null, + ]; + } + + $batchSize = 1000; + $chunks = array_chunk($mapToUpsert, $batchSize); + + foreach($chunks as $chunk){ + PbgTaskGoogleSheet::upsert($chunk, ["no_registrasi"],[ + 'jenis_konsultasi', + 'nama_pemilik', + 'lokasi_bg', + 'fungsi_bg', + 'nama_bangunan', + 'tgl_permohonan', + 'status_verifikasi', + 'status_permohonan', + 'alamat_pemilik', + 'no_hp', + 'email', + 'tanggal_catatan', + 'catatan_kekurangan_dokumen', + 'gambar', + 'krk_kkpr', + 'no_krk', + 'lh', + 'ska', + 'keterangan', + 'helpdesk', + 'pj', + 'kepemilikan', + 'potensi_taru', + 'validasi_dinas', + 'kategori_retribusi', + 'no_urut_ba_tpt', + 'tanggal_ba_tpt', + 'no_urut_ba_tpa', + 'tanggal_ba_tpa', + 'no_urut_skrd', + 'tanggal_skrd', + 'ptsp', + 'selesai_terbit', + 'tanggal_pembayaran', + 'format_sts', + 'tahun_terbit', + 'tahun_berjalan', + 'kelurahan', + 'kecamatan', + 'lb', + 'tb', + 'jlb', + 'unit', + 'usulan_retribusi', + 'nilai_retribusi_keseluruhan_simbg', + 'nilai_retribusi_keseluruhan_pad', + 'denda', + 'latitude', + 'longitude', + 'nik_nib', + 'dok_tanah', + 'temuan', + ]); + } $initResToken = $this->getToken(); if (empty($initResToken->original['data']['token']['access'])) { @@ -216,8 +382,8 @@ class ServiceSIMBG } } - BigdataResume::generateResumeData($importDatasource->id, "all"); - BigdataResume::generateResumeData($importDatasource->id, now()->year); + BigdataResume::generateResumeData($importDatasource->id, "all", $data_setting_result); + BigdataResume::generateResumeData($importDatasource->id, now()->year, $data_setting_result); // Final update after processing all pages $importDatasource->update([ @@ -323,5 +489,59 @@ class ServiceSIMBG throw $e; } } + protected function convertToDecimal(?string $value): ?float + { + if (empty($value)) { + return null; // Return null if the input is empty + } + // Remove all non-numeric characters except comma and dot + $value = preg_replace('/[^0-9,\.]/', '', $value); + + // If the number contains both dot (.) and comma (,) + if (strpos($value, '.') !== false && strpos($value, ',') !== false) { + $value = str_replace('.', '', $value); // Remove thousands separator + $value = str_replace(',', '.', $value); // Convert decimal separator to dot + } + // If only a dot is present (assumed as thousands separator) + elseif (strpos($value, '.') !== false) { + $value = str_replace('.', '', $value); // Remove all dots (treat as thousands separators) + } + // If only a comma is present (assumed as decimal separator) + elseif (strpos($value, ',') !== false) { + $value = str_replace(',', '.', $value); // Convert comma to dot (decimal separator) + } + + // Ensure the value is numeric before returning + return is_numeric($value) ? (float) number_format((float) $value, 2, '.', '') : null; + } + + protected function convertToInteger($value) { + // Check if the value is an empty string, and return null if true + if (trim($value) === "") { + return null; + } + + // Otherwise, cast to integer + return (int) $value; + } + + protected function convertToDate($dateString) + { + try { + // Check if the string is empty + if (empty($dateString)) { + return null; + } + + // Try to parse the date string + $date = Carbon::parse($dateString); + + // Return the Carbon instance + return $date->format('Y-m-d'); + } catch (Exception $e) { + // Return null if an error occurs during parsing + return null; + } + } } \ No newline at end of file diff --git a/database/migrations/2025_03_03_174851_add_some_column_in_bigdata_resumes.php b/database/migrations/2025_03_03_174851_add_some_column_in_bigdata_resumes.php new file mode 100644 index 0000000..bc12814 --- /dev/null +++ b/database/migrations/2025_03_03_174851_add_some_column_in_bigdata_resumes.php @@ -0,0 +1,38 @@ +integer('waiting_click_dpmptsp_count')->default(0); + $table->decimal('waiting_click_dpmptsp_sum', 20,2)->default(0); + $table->integer('issuance_realization_pbg_count')->default(0); + $table->decimal('issuance_realization_pbg_sum', 20,2)->default(0); + $table->integer('process_in_technical_office_count')->default(0); + $table->decimal('process_in_technical_office_sum', 20,2)->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('bigdata_resumes', function (Blueprint $table) { + $table->dropColumn('waiting_click_dpmptsp_count'); + $table->dropColumn('waiting_click_dpmptsp_sum'); + $table->dropColumn('issuance_realization_pbg_count'); + $table->dropColumn('issuance_realization_pbg_sum'); + $table->dropColumn('process_in_technical_office_count'); + $table->dropColumn('process_in_technical_office_sum'); + }); + } +}; diff --git a/resources/js/bigdata-resumes/index.js b/resources/js/bigdata-resumes/index.js index a984601..359add3 100644 --- a/resources/js/bigdata-resumes/index.js +++ b/resources/js/bigdata-resumes/index.js @@ -34,18 +34,18 @@ class BigdataResume { this.table = new Grid({ columns: [ { name: "ID" }, - { name: "Potention Count" }, - { name: "Potention Sum" }, - { name: "Non Verified Count" }, - { name: "Non Verified Sum" }, - { name: "Verified Count" }, - { name: "Verified Sum" }, - { name: "Business Count" }, - { name: "Business Sum" }, - { name: "Non Business Count" }, - { name: "Non Business Sum" }, - { name: "Spatial Sum" }, - { name: "Spatial Count" }, + { name: "Jumlah Potensi" }, + { name: "Total Potensi" }, + { name: "Jumlah Berkas Belum Terverifikasi" }, + { name: "Total Berkas Belum Terverifikasi" }, + { name: "Jumlah Berkas Terverifikasi" }, + { name: "Total Berkas Terverifikasi" }, + { name: "Jumlah Usaha" }, + { name: "Total Usaha" }, + { name: "Jumlah Non Usaha" }, + { name: "Total Non Usaha" }, + { name: "Jumlah Tata Ruang" }, + { name: "Total Tata Ruang" }, { name: "Created", attributes: { style: "width: 200px; white-space: nowrap;" }, // Set width dynamically diff --git a/resources/js/dashboards/lack-of-potential.js b/resources/js/dashboards/lack-of-potential.js index df5d9b4..ab6d071 100644 --- a/resources/js/dashboards/lack-of-potential.js +++ b/resources/js/dashboards/lack-of-potential.js @@ -16,6 +16,19 @@ class LackOfPotential { 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( @@ -140,6 +153,15 @@ class LackOfPotential { 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) { diff --git a/resources/js/settings/syncronize/syncronize.js b/resources/js/settings/syncronize/syncronize.js index 6430ed3..61dec35 100644 --- a/resources/js/settings/syncronize/syncronize.js +++ b/resources/js/settings/syncronize/syncronize.js @@ -4,13 +4,21 @@ import "gridjs/dist/gridjs.umd.js"; import GlobalConfig from "../../global-config.js"; class SyncronizeTask { + constructor() { + this.toastElement = document.getElementById("toastNotification"); + this.toastMessage = document.getElementById("toast-message"); + this.toast = new bootstrap.Toast(this.toastElement); + this.table = null; + } init() { this.initTableImportDatasources(); this.handleSubmitSync(); - this.handleSubmitSnycGoogleSheet(); } initTableImportDatasources() { - new Grid({ + let tableContainer = document.getElementById( + "table-import-datasources" + ); + this.table = new gridjs.Grid({ columns: ["ID", "Message", "Response", "Status", "Created"], search: { server: { @@ -45,20 +53,24 @@ class SyncronizeTask { ]), total: (data) => data.meta.total, }, - }).render(document.getElementById("table-import-datasources")); + }).render(tableContainer); } handleSubmitSync() { const button = document.getElementById("btn-sync-submit"); + const spinner = document.getElementById("spinner"); + const apiToken = document + .querySelector('meta[name="api-token"]') + .getAttribute("content"); + + // Show the spinner while checking + spinner.classList.remove("d-none"); - // Check if the button should be enabled or disabled based on the status fetch( `${GlobalConfig.apiHost}/api/import-datasource/check-datasource`, { method: "GET", headers: { - Authorization: `Bearer ${document - .querySelector('meta[name="api-token"]') - .getAttribute("content")}`, + Authorization: `Bearer ${apiToken}`, "Content-Type": "application/json", }, } @@ -70,49 +82,21 @@ class SyncronizeTask { return response.json(); }) .then((data) => { - console.log("data check button sync", data.can_execute); button.disabled = !data.can_execute; - // If the button is enabled, add click event to trigger sync - if (!button.disabled) { - button.addEventListener("click", function (e) { - button.disabled = true; // Disable button to prevent multiple clicks - button.textContent = "Syncing..."; // Change button text to show syncing + if (!data.can_execute) { + // Keep spinner visible if cannot execute + spinner.classList.remove("d-none"); + } else { + // Hide spinner when execution is allowed + spinner.classList.add("d-none"); - // Trigger the scraping API call - fetch(`${GlobalConfig.apiHost}/api/scraping`, { - method: "GET", - headers: { - Authorization: `Bearer ${document - .querySelector('meta[name="api-token"]') - .getAttribute("content")}`, - "Content-Type": "application/json", - }, - }) - .then((response) => { - if (!response.ok) { - throw new Error( - "Network response was not ok" - ); - } - return response.json(); - }) - .then((data) => { - console.log("data sync button", data); - alert("Synchronization executed successfully"); - window.location.reload(); - }) - .catch((err) => { - console.error("Fetch error:", err); - alert( - "An error occurred during synchronization" - ); - }) - .finally(() => { - button.disabled = false; // Re-enable the button after the request is complete - button.textContent = "Sync Data"; // Reset button text - }); - }); + // Remove previous event listener before adding a new one + button.removeEventListener("click", this.handleSyncClick); + button.addEventListener( + "click", + this.handleSyncClick.bind(this) + ); } }) .catch((err) => { @@ -120,42 +104,53 @@ class SyncronizeTask { alert("An error occurred while checking the datasource"); }); } - handleSubmitSnycGoogleSheet() { - const button = document.getElementById("btn-sync-submit-google-sheet"); - button.addEventListener("click", function (e) { - button.disabled = true; // Disable button to prevent multiple clicks - button.textContent = "Syncing..."; // Change button text to show syncing - // Trigger the scraping API call - fetch(`${GlobalConfig.apiHost}/api/sync-pbg-task-google-sheet`, { - method: "GET", - headers: { - Authorization: `Bearer ${document - .querySelector('meta[name="api-token"]') - .getAttribute("content")}`, - "Content-Type": "application/json", - }, + handleSyncClick() { + const button = document.getElementById("btn-sync-submit"); + const spinner = document.getElementById("spinner"); + const apiToken = document + .querySelector('meta[name="api-token"]') + .getAttribute("content"); + + button.disabled = true; // Prevent multiple clicks + spinner.classList.remove("d-none"); // Show spinner during sync + + fetch(`${GlobalConfig.apiHost}/api/scraping`, { + method: "GET", + headers: { + Authorization: `Bearer ${apiToken}`, + "Content-Type": "application/json", + }, + }) + .then(async (response) => { + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + let data; + try { + data = await response.json(); + } catch (jsonError) { + throw new Error("Failed to parse JSON response"); + } + + return data; }) - .then((response) => { - if (!response.ok) { - throw new Error("Network response was not ok"); - } - return response.json(); - }) - .then((data) => { - console.log("data sync button", data); - alert("Synchronization executed successfully"); - window.location.reload(); - }) - .catch((err) => { - console.error("Fetch error:", err); - alert("An error occurred during synchronization"); - }) - .finally(() => { - button.disabled = false; // Re-enable the button after the request is complete - button.textContent = "Sync Google Sheet"; // Reset button text - }); - }); + .then((data) => { + this.toastMessage.innerText = + data.data.message || "Synchronize successfully!"; + this.toast.show(); + + // Update the table if it exists + if (this.table) { + this.table.updateConfig({}).forceRender(); + } + }) + .catch((err) => { + console.error("Fetch error:", err); + alert("An error occurred during synchronization" + err.message); + button.disabled = false; + }); } } document.addEventListener("DOMContentLoaded", function (e) { diff --git a/resources/views/dashboards/lack_of_potential.blade.php b/resources/views/dashboards/lack_of_potential.blade.php index 0be6921..6693651 100644 --- a/resources/views/dashboards/lack_of_potential.blade.php +++ b/resources/views/dashboards/lack_of_potential.blade.php @@ -21,7 +21,7 @@
- +
@@ -52,11 +52,11 @@
- + - +
@@ -74,23 +74,20 @@ 'visible_small_circle' => false, 'style' => 'margin-left:180px;top:-20px;' ]) - @endcomponent - - - -
+ @endcomponent + +
-
-
-
- - - -
+
+
+
+ + +
- + -
+
@@ -105,7 +102,7 @@
- +
diff --git a/resources/views/layouts/vertical.blade.php b/resources/views/layouts/vertical.blade.php index 3328b33..fbe5bef 100755 --- a/resources/views/layouts/vertical.blade.php +++ b/resources/views/layouts/vertical.blade.php @@ -3,32 +3,57 @@ diff --git a/resources/views/settings/syncronize/index.blade.php b/resources/views/settings/syncronize/index.blade.php index 82e4b80..8287666 100644 --- a/resources/views/settings/syncronize/index.blade.php +++ b/resources/views/settings/syncronize/index.blade.php @@ -7,14 +7,16 @@ @section('content') @include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Syncronize']) - +
- - +
From ee1a395c75894d2c49fc4e452abaf0c8a08fbed1 Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 10:41:52 +0700 Subject: [PATCH 02/15] add per page 50 and add some column onn laporan pimpinan --- .../Api/BigDataResumeController.php | 34 +------------------ .../Api/BusinessOrIndustriesController.php | 2 +- .../Controllers/Api/CustomersController.php | 2 +- .../Api/ImportDatasourceController.php | 2 +- app/Http/Controllers/Api/MenusController.php | 2 +- app/Http/Controllers/Api/RolesController.php | 2 +- app/Http/Controllers/Api/UsersController.php | 2 +- app/Http/Resources/BigdataResumeResource.php | 11 +++++- config/app.php | 3 +- resources/js/bigdata-resumes/index.js | 26 +++++++++++--- resources/js/business-industries/index.js | 2 +- resources/js/customers/index.js | 2 +- resources/js/master/users/users.js | 2 +- resources/js/menus/index.js | 2 +- resources/js/roles/index.js | 2 +- .../js/settings/syncronize/syncronize.js | 2 +- 16 files changed, 46 insertions(+), 52 deletions(-) diff --git a/app/Http/Controllers/Api/BigDataResumeController.php b/app/Http/Controllers/Api/BigDataResumeController.php index 98734c6..972f234 100644 --- a/app/Http/Controllers/Api/BigDataResumeController.php +++ b/app/Http/Controllers/Api/BigDataResumeController.php @@ -159,7 +159,7 @@ class BigDataResumeController extends Controller $query->where('name', 'LIKE', '%'.$request->input('search').'%'); } - $query = $query->paginate(15); + $query = $query->paginate(config('app.paginate_per_page', 50)); return BigdataResumeResource::collection($query)->response()->getData(true); }catch(\Exception $e){ Log::error($e->getMessage()); @@ -167,38 +167,6 @@ class BigDataResumeController extends Controller } } - /** - * Store a newly created resource in storage. - */ - public function store(Request $request) - { - // - } - - /** - * Display the specified resource. - */ - public function show(string $id) - { - // - } - - /** - * Update the specified resource in storage. - */ - public function update(Request $request, string $id) - { - // - } - - /** - * Remove the specified resource from storage. - */ - public function destroy(string $id) - { - // - } - private function response_empty_resume(){ $result = [ 'target_pad' => [ diff --git a/app/Http/Controllers/Api/BusinessOrIndustriesController.php b/app/Http/Controllers/Api/BusinessOrIndustriesController.php index d426b0b..0002eee 100644 --- a/app/Http/Controllers/Api/BusinessOrIndustriesController.php +++ b/app/Http/Controllers/Api/BusinessOrIndustriesController.php @@ -31,7 +31,7 @@ class BusinessOrIndustriesController extends Controller }); } - return response()->json($query->paginate()); + return response()->json($query->paginate(config('app.paginate_per_page', 50))); } /** diff --git a/app/Http/Controllers/Api/CustomersController.php b/app/Http/Controllers/Api/CustomersController.php index 33b5d44..248ad42 100644 --- a/app/Http/Controllers/Api/CustomersController.php +++ b/app/Http/Controllers/Api/CustomersController.php @@ -24,7 +24,7 @@ class CustomersController extends Controller ->orWhere('nama', 'LIKE', '%'.$request->get('search').'%') ->orWhere('kota_pelayanan', 'LIKE', '%'.$request->get('search').'%'); } - return CustomersResource::collection($query->paginate()); + return CustomersResource::collection($query->paginate(config('app.paginate_per_page', 50))); } /** diff --git a/app/Http/Controllers/Api/ImportDatasourceController.php b/app/Http/Controllers/Api/ImportDatasourceController.php index f77c4a0..ed48ef2 100644 --- a/app/Http/Controllers/Api/ImportDatasourceController.php +++ b/app/Http/Controllers/Api/ImportDatasourceController.php @@ -24,7 +24,7 @@ class ImportDatasourceController extends Controller $search = $request->get("search"); $query->where('status', 'like', "%".$search."%"); } - return ImportDatasourceResource::collection($query->paginate()); + return ImportDatasourceResource::collection($query->paginate(config('app.paginate_per_page', 50))); } public function checkImportDatasource(){ diff --git a/app/Http/Controllers/Api/MenusController.php b/app/Http/Controllers/Api/MenusController.php index eede7aa..50fd187 100644 --- a/app/Http/Controllers/Api/MenusController.php +++ b/app/Http/Controllers/Api/MenusController.php @@ -22,7 +22,7 @@ class MenusController extends Controller $query = $query->where("name", "like", "%".$request->get("search")."%"); } - return response()->json($query->paginate()); + return response()->json($query->paginate(config('app.paginate_per_page', 50))); } /** diff --git a/app/Http/Controllers/Api/RolesController.php b/app/Http/Controllers/Api/RolesController.php index d005214..0e755f2 100644 --- a/app/Http/Controllers/Api/RolesController.php +++ b/app/Http/Controllers/Api/RolesController.php @@ -20,7 +20,7 @@ class RolesController extends Controller $query = $query->where('name', 'like', '%'. $request->get('search') . '%'); } - return response()->json($query->paginate()); + return response()->json($query->paginate(config('app.paginate_per_page', 50))); } /** diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index c4c97f1..da5c6f4 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -31,7 +31,7 @@ class UsersController extends Controller if($request->has('search') && !empty($request->get("search"))){ $query->where('name', 'LIKE', '%'.$request->get('search').'%'); } - return UserResource::collection($query->paginate()); + return UserResource::collection($query->paginate(config('app.paginate_per_page', 50))); } public function logout(Request $request){ $request->user()->tokens()->delete(); diff --git a/app/Http/Resources/BigdataResumeResource.php b/app/Http/Resources/BigdataResumeResource.php index 611f77e..400ca30 100644 --- a/app/Http/Resources/BigdataResumeResource.php +++ b/app/Http/Resources/BigdataResumeResource.php @@ -34,7 +34,16 @@ class BigdataResumeResource extends JsonResource 'spatial_count' => (int) $this->spatial_count, 'spatial_sum' => number_format((float) $this->spatial_sum, 2, ',', '.'), - + + 'issuance_realization_pbg_count' => (int) $this->issuance_realization_pbg_count, + 'issuance_realization_pbg_sum' => number_format((float) $this->issuance_realization_pbg_sum, 2, ',', '.'), + + 'waiting_click_dpmptsp_count' => (int) $this->waiting_click_dpmptsp_count, + 'waiting_click_dpmptsp_sum' => number_format((float) $this->waiting_click_dpmptsp_sum, 2, ',', '.'), + + 'process_in_technical_office_count' => (int) $this->process_in_technical_office_count, + 'process_in_technical_office_sum' => number_format((float) $this->process_in_technical_office_sum, 2, ',', '.'), + 'year' => $this->year, 'created_at' => $this->created_at->toDateTimeString(), ]; diff --git a/config/app.php b/config/app.php index ea06b04..1b40396 100755 --- a/config/app.php +++ b/config/app.php @@ -123,5 +123,6 @@ return [ 'store' => env('APP_MAINTENANCE_STORE', 'database'), ], - 'api_url' => env('API_URL', 'http://localhost:8000') + 'api_url' => env('API_URL', 'http://localhost:8000'), + 'paginate_per_page' => 50 ]; diff --git a/resources/js/bigdata-resumes/index.js b/resources/js/bigdata-resumes/index.js index 359add3..b2cb7a4 100644 --- a/resources/js/bigdata-resumes/index.js +++ b/resources/js/bigdata-resumes/index.js @@ -30,7 +30,6 @@ class BigdataResume { initTableDataSettings() { let tableContainer = document.getElementById("table-bigdata-resumes"); - // Create a new Grid.js instance only if it doesn't exist this.table = new Grid({ columns: [ { name: "ID" }, @@ -46,13 +45,19 @@ class BigdataResume { { name: "Total Non Usaha" }, { name: "Jumlah Tata Ruang" }, { name: "Total Tata Ruang" }, + { name: "Jumlah Menunggu Klik DPMPTSP" }, + { name: "Total Menunggu Klik DPMPTSP" }, + { name: "Jumlah Realisasi Terbit PBG" }, + { name: "Total Realisasi Terbit PBG" }, + { name: "Jumlah Proses Dinas Teknis" }, + { name: "Total Proses Dinas Teknis" }, { name: "Created", attributes: { style: "width: 200px; white-space: nowrap;" }, // Set width dynamically }, ], pagination: { - limit: 15, + limit: 50, server: { url: (prev, page) => `${prev}${prev.includes("?") ? "&" : "?"}page=${ @@ -74,8 +79,8 @@ class BigdataResume { .getAttribute("content")}`, "Content-Type": "application/json", }, - then: (data) => - data.data.map((item) => [ + then: (data) => { + return data.data.map((item) => [ item.id, item.potention_count, addThousandSeparators(item.potention_sum), @@ -89,8 +94,19 @@ class BigdataResume { addThousandSeparators(item.non_business_sum), item.spatial_count, addThousandSeparators(item.spatial_sum), + item.waiting_click_dpmptsp_count, + addThousandSeparators(item.waiting_click_dpmptsp_sum), + item.issuance_realization_pbg_count, + addThousandSeparators( + item.issuance_realization_pbg_sum + ), + item.process_in_technical_office_count, + addThousandSeparators( + item.process_in_technical_office_sum + ), moment(item.created_at).format("YYYY-MM-DD H:mm:ss"), - ]), + ]); + }, total: (data) => data.total, }, }).render(tableContainer); diff --git a/resources/js/business-industries/index.js b/resources/js/business-industries/index.js index a85eb04..bcba970 100644 --- a/resources/js/business-industries/index.js +++ b/resources/js/business-industries/index.js @@ -64,7 +64,7 @@ class BusinessIndustries { }, ], pagination: { - limit: 15, + limit: 50, server: { url: (prev, page) => `${prev}${prev.includes("?") ? "&" : "?"}page=${ diff --git a/resources/js/customers/index.js b/resources/js/customers/index.js index e2f5835..70b0b65 100644 --- a/resources/js/customers/index.js +++ b/resources/js/customers/index.js @@ -53,7 +53,7 @@ class Customers { }, ], pagination: { - limit: 15, + limit: 50, server: { url: (prev, page) => `${prev}${prev.includes("?") ? "&" : "?"}page=${ diff --git a/resources/js/master/users/users.js b/resources/js/master/users/users.js index 15dab30..5506e06 100644 --- a/resources/js/master/users/users.js +++ b/resources/js/master/users/users.js @@ -31,7 +31,7 @@ class UsersTable { }, ], pagination: { - limit: 15, + limit: 50, server: { url: (prev, page) => `${prev}${prev.includes("?") ? "&" : "?"}page=${ diff --git a/resources/js/menus/index.js b/resources/js/menus/index.js index a2797c6..92109c5 100644 --- a/resources/js/menus/index.js +++ b/resources/js/menus/index.js @@ -83,7 +83,7 @@ class Menus { }, ], pagination: { - limit: 15, + limit: 50, server: { url: (prev, page) => `${prev}${prev.includes("?") ? "&" : "?"}page=${ diff --git a/resources/js/roles/index.js b/resources/js/roles/index.js index a6bfc2a..c4ab98d 100644 --- a/resources/js/roles/index.js +++ b/resources/js/roles/index.js @@ -52,7 +52,7 @@ class Roles { }, ], pagination: { - limit: 15, + limit: 50, server: { url: (prev, page) => `${prev}${prev.includes("?") ? "&" : "?"}page=${ diff --git a/resources/js/settings/syncronize/syncronize.js b/resources/js/settings/syncronize/syncronize.js index 61dec35..7d62b55 100644 --- a/resources/js/settings/syncronize/syncronize.js +++ b/resources/js/settings/syncronize/syncronize.js @@ -26,7 +26,7 @@ class SyncronizeTask { }, }, pagination: { - limit: 15, + limit: 50, server: { url: (prev, page) => `${prev}${prev.includes("?") ? "&" : "?"}page=${ From cdd84d02da64ee53738e19dc8a8992efb547bf15 Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 14:30:41 +0700 Subject: [PATCH 03/15] fix parsing separator --- .../Api/BigDataResumeController.php | 26 ++++++++++++------- .../Api/RequestAssignmentController.php | 3 ++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/Api/BigDataResumeController.php b/app/Http/Controllers/Api/BigDataResumeController.php index 972f234..b1b3e86 100644 --- a/app/Http/Controllers/Api/BigDataResumeController.php +++ b/app/Http/Controllers/Api/BigDataResumeController.php @@ -39,13 +39,17 @@ class BigDataResumeController extends Controller return response()->json(['message' => 'No data setting found']); } + function cleanNumber($value) { + return floatval(str_replace('.', '', $value)); + } + $target_pad = floatval(optional($data_settings->where('key', 'TARGET_PAD')->first())->value); - $realisasi_terbit_pbg_sum = floatval(optional($data_settings->where('key', 'REALISASI_TERBIT_PBG_SUM')->first())->value); - $realisasi_terbit_pbg_count = floatval(optional($data_settings->where('key', 'REALISASI_TERBIT_PBG_COUNT')->first())->value); - $menuggu_klik_dpmptsp_sum = floatval(optional($data_settings->where('key', 'MENUNGGU_KLIK_DPMPTSP_SUM')->first())->value); - $menuggu_klik_dpmptsp_count = floatval(optional($data_settings->where('key', 'MENUNGGU_KLIK_DPMPTSP_COUNT')->first())->value); - $proses_dinas_teknis_sum = floatval(optional($data_settings->where('key', 'PROSES_DINAS_TEKNIS_SUM')->first())->value); - $proses_dinas_teknis_count = floatval(optional($data_settings->where('key', 'PROSES_DINAS_TEKNIS_COUNT')->first())->value); + $realisasi_terbit_pbg_sum = cleanNumber(optional($data_settings->where('key', 'REALISASI_TERBIT_PBG_SUM')->first())->value); + $realisasi_terbit_pbg_count = cleanNumber(optional($data_settings->where('key', 'REALISASI_TERBIT_PBG_COUNT')->first())->value); + $menunggu_klik_dpmptsp_sum = cleanNumber(optional($data_settings->where('key', 'MENUNGGU_KLIK_DPMPTSP_SUM')->first())->value); + $menunggu_klik_dpmptsp_count = cleanNumber(optional($data_settings->where('key', 'MENUNGGU_KLIK_DPMPTSP_COUNT')->first())->value); + $proses_dinas_teknis_sum = cleanNumber(optional($data_settings->where('key', 'PROSES_DINAS_TEKNIS_SUM')->first())->value); + $proses_dinas_teknis_count = cleanNumber(optional($data_settings->where('key', 'PROSES_DINAS_TEKNIS_COUNT')->first())->value); $tata_ruang = $big_data_resume->spatial_sum; $kekurangan_potensi = $target_pad - $big_data_resume->potention_sum; @@ -83,8 +87,8 @@ class BigDataResumeController extends Controller ? round(($realisasi_terbit_pbg_sum / $big_data_resume->verified_sum) * 100, 2) : 0; // percentage menunggu klik dpmptsp - $menunggu_klik_dpmptsp_percentage = $big_data_resume->verified_sum > 0 && $menuggu_klik_dpmptsp_sum > 0 - ? round(($menuggu_klik_dpmptsp_sum / $big_data_resume->verified_sum) * 100, 2) : 0; + $menunggu_klik_dpmptsp_percentage = $big_data_resume->verified_sum > 0 && $menunggu_klik_dpmptsp_sum > 0 + ? round(($menunggu_klik_dpmptsp_sum / $big_data_resume->verified_sum) * 100, 2) : 0; // percentage proses_dinas_teknis $proses_dinas_teknis_percentage = $big_data_resume->verified_sum > 0 && $proses_dinas_teknis_sum > 0 @@ -135,8 +139,8 @@ class BigDataResumeController extends Controller 'percentage' => $realisasi_terbit_percentage ], 'menunggu_klik_dpmptsp' => [ - 'sum' => $menuggu_klik_dpmptsp_sum, - 'count' => $menuggu_klik_dpmptsp_count, + 'sum' => $menunggu_klik_dpmptsp_sum, + 'count' => $menunggu_klik_dpmptsp_count, 'percentage' => $menunggu_klik_dpmptsp_percentage ], 'proses_dinas_teknis' => [ @@ -151,6 +155,8 @@ class BigDataResumeController extends Controller } } + + public function bigdata_report(Request $request){ try{ $query = BigdataResume::query()->orderBy('id', 'desc'); diff --git a/app/Http/Controllers/Api/RequestAssignmentController.php b/app/Http/Controllers/Api/RequestAssignmentController.php index 56605f5..3474fe5 100644 --- a/app/Http/Controllers/Api/RequestAssignmentController.php +++ b/app/Http/Controllers/Api/RequestAssignmentController.php @@ -17,7 +17,8 @@ class RequestAssignmentController extends Controller $query = PbgTask::query()->orderBy('id', 'desc'); if($request->has('search') && !empty($request->get("search"))){ $query->where('name', 'LIKE', '%'.$request->get('search').'%') - ->orWhere('registration_number', 'LIKE', '%'.$request->get('search').'%'); + ->orWhere('registration_number', 'LIKE', '%'.$request->get('search').'%') + ->orWhere('document_number', 'LIKE', '%'.$request->get('search').'%'); } return RequestAssignmentResouce::collection($query->paginate()); } From c5e3fdd9157238e70a784550a357d11be0a010d4 Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 15:05:13 +0700 Subject: [PATCH 04/15] create file automation deployment using sh --- deploy.sh | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 deploy.sh diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..0c7b0f0 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,38 @@ +GIT_BRANCH="dev" +PHP_VERSION="php8.3" + +echo "🚀 Starting deployment..." +php artisan down + +echo "📥 Pulling latest changes from Git..." +git fetch origin $GIT_BRANCH +git reset --hard origin/$GIT_BRANCH +git pull origin $GIT_BRANCH + +echo "⚡ Installing NPM dependencies and building assets..." +npm ci --no-audit --no-fund +npm run build + +echo "📦 Installing composer dependencies..." +composer install --no-interaction --optimize-autoloader + +echo "🗄️ Running migrations..." +php artisan migrate --force + +echo "⚡ Optimizing application..." +php artisan cache:clear +php artisan config:clear +php artisan config:cache +php artisan route:cache +php artisan view:cache + +echo "🔄 Restarting PHP service..." +systemctl restart $PHP_VERSION-fpm + +echo "🔁 Restarting Supervisor queue workers..." +supervisorctl stop all +supervisorctl reload +supervisorctl start all + +php artisan up +echo "🚀 Deployment completed successfully!" \ No newline at end of file From c0faafdbd7cdb3f27176c3875153b95a19dfd87c Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 15:13:33 +0700 Subject: [PATCH 05/15] hot fix add time midnight scheduler --- routes/console.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/console.php b/routes/console.php index e718d8f..68855b6 100755 --- a/routes/console.php +++ b/routes/console.php @@ -8,4 +8,4 @@ Artisan::command('inspire', function () { $this->comment(Inspiring::quote()); })->purpose('Display an inspiring quote')->hourly(); -Schedule::command("app:execute-scraping")->daily(); \ No newline at end of file +Schedule::command("app:execute-scraping")->dailyAt("00:00"); \ No newline at end of file From b4ec7a9d25453ec1e7d160da47f20a68a48767fa Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 15:19:11 +0700 Subject: [PATCH 06/15] merge chatbot sidebar --- vite.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/vite.config.js b/vite.config.js index d0cac80..5fc9c25 100755 --- a/vite.config.js +++ b/vite.config.js @@ -100,6 +100,7 @@ export default defineConfig({ // laporan pimpinan "resources/js/bigdata-resumes/index.js", "resources/js/chatbot/index.js", + "resources/js/chatbot-pimpinan/index.js", ], refresh: true, }), From d6d0acf8fbb201e8cbf0459c0ce7458c45f7540e Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 15:46:35 +0700 Subject: [PATCH 07/15] hot fix conflict routing web and api advertisements --- .../Data/AdvertisementController.php | 2 +- database/seeders/UsersRoleMenuSeeder.php | 2 +- .../data/advertisements/form-create-update.js | 232 +++++++++--------- .../js/data/advertisements/form-upload.js | 134 +++++----- routes/web.php | 4 +- 5 files changed, 196 insertions(+), 178 deletions(-) diff --git a/app/Http/Controllers/Data/AdvertisementController.php b/app/Http/Controllers/Data/AdvertisementController.php index 83606cc..8140ad2 100644 --- a/app/Http/Controllers/Data/AdvertisementController.php +++ b/app/Http/Controllers/Data/AdvertisementController.php @@ -62,7 +62,7 @@ class AdvertisementController extends Controller // Pastikan model ditemukan if (!$modelInstance) { info("AdvertisementController@edit: Model tidak ditemukan."); - return redirect()->route('advertisements.index')->with('error', 'Advertisement not found'); + return redirect()->route('web.advertisements.index')->with('error', 'Advertisement not found'); } // Mengambil dan memetakan village_name dan district_name diff --git a/database/seeders/UsersRoleMenuSeeder.php b/database/seeders/UsersRoleMenuSeeder.php index 9e4667d..02cb854 100644 --- a/database/seeders/UsersRoleMenuSeeder.php +++ b/database/seeders/UsersRoleMenuSeeder.php @@ -167,7 +167,7 @@ class UsersRoleMenuSeeder extends Seeder ], [ "name" => "Reklame", - "url" => "advertisements.index", + "url" => "web.advertisements.index", "icon" => null, "parent_id" => $data->id, "sort_order" => 2, diff --git a/resources/js/data/advertisements/form-create-update.js b/resources/js/data/advertisements/form-create-update.js index 95a2edc..17a37ac 100644 --- a/resources/js/data/advertisements/form-create-update.js +++ b/resources/js/data/advertisements/form-create-update.js @@ -3,7 +3,7 @@ import GlobalConfig from "../../global-config"; document.addEventListener("DOMContentLoaded", function () { const saveButton = document.querySelector(".modal-footer .btn-primary"); const modalButton = document.querySelector(".btn-modal"); - const form = document.querySelector("form#create-update-form"); + const form = document.querySelector("form#create-update-form"); var authLogo = document.querySelector(".auth-logo"); if (!saveButton || !form) return; @@ -16,10 +16,10 @@ document.addEventListener("DOMContentLoaded", function () { Loading... `; const isEdit = saveButton.classList.contains("btn-edit"); - const formData = new FormData(form) - const toast = document.getElementById('toastEditUpdate'); - const toastBody = toast.querySelector('.toast-body'); - const toastHeader = toast.querySelector('.toast-header small'); + const formData = new FormData(form); + const toast = document.getElementById("toastEditUpdate"); + const toastBody = toast.querySelector(".toast-body"); + const toastHeader = toast.querySelector(".toast-header small"); const data = {}; @@ -27,9 +27,9 @@ document.addEventListener("DOMContentLoaded", function () { formData.forEach((value, key) => { data[key] = value; }); - + const url = form.getAttribute("action"); - + const method = isEdit ? "PUT" : "POST"; fetch(url, { @@ -40,99 +40,103 @@ document.addEventListener("DOMContentLoaded", function () { .querySelector('meta[name="api-token"]') .getAttribute("content")}`, "Content-Type": "application/json", - } + }, }) - .then(response => response.json()) - .then(data => { - if (!data.errors) { - // Remove existing icon (if any) before adding the new one - if (authLogo) { - // Hapus ikon yang sudah ada jika ada - const existingIcon = authLogo.querySelector('.bx'); - if (existingIcon) { - authLogo.removeChild(existingIcon); - } - - // Buat ikon baru - const icon = document.createElement('i'); - icon.classList.add('bx', 'bxs-check-square'); - icon.style.fontSize = '25px'; - icon.style.color = 'green'; // Pastikan 'green' dalam bentuk string - - // Tambahkan ikon ke dalam auth-logo - authLogo.appendChild(icon); - } - - // Set success message for the toast - toastBody.textContent = isEdit ? "Data updated successfully!" : "Data created successfully!"; - toast.classList.add('show'); // Show the toast - setTimeout(() => { - toast.classList.remove('show'); // Hide the toast after 3 seconds - }, 2000); - - setTimeout(() => { - window.location.href = '/data/advertisements'; - }, 1000); - } else { - if (authLogo) { - // Hapus ikon yang sudah ada jika ada - const existingIcon = authLogo.querySelector('.bx'); - if (existingIcon) { - authLogo.removeChild(existingIcon); - } - - // Buat ikon baru - const icon = document.createElement('i'); - icon.classList.add('bx', 'bxs-error-alt'); - icon.style.fontSize = '25px'; - icon.style.color = 'red'; // Pastikan 'green' dalam bentuk string - - // Tambahkan ikon ke dalam auth-logo - authLogo.appendChild(icon); - } + .then((response) => response.json()) + .then((data) => { + if (!data.errors) { + // Remove existing icon (if any) before adding the new one + if (authLogo) { + // Hapus ikon yang sudah ada jika ada + const existingIcon = authLogo.querySelector(".bx"); + if (existingIcon) { + authLogo.removeChild(existingIcon); + } + // Buat ikon baru + const icon = document.createElement("i"); + icon.classList.add("bx", "bxs-check-square"); + icon.style.fontSize = "25px"; + icon.style.color = "green"; // Pastikan 'green' dalam bentuk string + + // Tambahkan ikon ke dalam auth-logo + authLogo.appendChild(icon); + } + + // Set success message for the toast + toastBody.textContent = isEdit + ? "Data updated successfully!" + : "Data created successfully!"; + toast.classList.add("show"); // Show the toast + setTimeout(() => { + toast.classList.remove("show"); // Hide the toast after 3 seconds + }, 2000); + + setTimeout(() => { + window.location.href = "/data/advertisements/index"; + }, 1000); + } else { + if (authLogo) { + // Hapus ikon yang sudah ada jika ada + const existingIcon = authLogo.querySelector(".bx"); + if (existingIcon) { + authLogo.removeChild(existingIcon); + } + + // Buat ikon baru + const icon = document.createElement("i"); + icon.classList.add("bx", "bxs-error-alt"); + icon.style.fontSize = "25px"; + icon.style.color = "red"; // Pastikan 'green' dalam bentuk string + + // Tambahkan ikon ke dalam auth-logo + authLogo.appendChild(icon); + } + + // Enable button and reset its text on error + modalButton.disabled = false; + modalButton.innerHTML = isEdit ? "Update" : "Create"; + + // Set error message for the toast + toastBody.textContent = + "Failed: " + (data.message || "Something went wrong"); + toast.classList.add("show"); // Show the toast + + setTimeout(() => { + toast.classList.remove("show"); // Hide the toast after 3 seconds + }, 3000); + } + }) + .catch((errors) => { + if (authLogo) { + // Hapus ikon yang sudah ada jika ada + const existingIcon = authLogo.querySelector(".bx"); + if (existingIcon) { + authLogo.removeChild(existingIcon); + } + + // Buat ikon baru + const icon = document.createElement("i"); + icon.classList.add("bx", "bxs-error-alt"); + icon.style.fontSize = "25px"; + icon.style.color = "red"; // Pastikan 'green' dalam bentuk string + + // Tambahkan ikon ke dalam auth-logo + authLogo.appendChild(icon); + } // Enable button and reset its text on error modalButton.disabled = false; - modalButton.innerHTML = isEdit ? "Update" : "Create"; + modalButton.innerHTML = isEdit ? "Update" : "Create"; // Set error message for the toast - toastBody.textContent = "Failed: " + (data.message || "Something went wrong"); - toast.classList.add('show'); // Show the toast + toastBody.textContent = + "An error occurred while processing your request."; + toast.classList.add("show"); // Show the toast setTimeout(() => { - toast.classList.remove('show'); // Hide the toast after 3 seconds + toast.classList.remove("show"); // Hide the toast after 3 seconds }, 3000); - } - }) - .catch(errors => { - if (authLogo) { - // Hapus ikon yang sudah ada jika ada - const existingIcon = authLogo.querySelector('.bx'); - if (existingIcon) { - authLogo.removeChild(existingIcon); - } - - // Buat ikon baru - const icon = document.createElement('i'); - icon.classList.add('bx', 'bxs-error-alt'); - icon.style.fontSize = '25px'; - icon.style.color = 'red'; // Pastikan 'green' dalam bentuk string - - // Tambahkan ikon ke dalam auth-logo - authLogo.appendChild(icon); - } - // Enable button and reset its text on error - modalButton.disabled = false; - modalButton.innerHTML = isEdit ? "Update" : "Create"; - - // Set error message for the toast - toastBody.textContent = "An error occurred while processing your request."; - toast.classList.add('show'); // Show the toast - - setTimeout(() => { - toast.classList.remove('show'); // Hide the toast after 3 seconds - }, 3000); - }); + }); }); // Fungsi fetchOptions untuk autocomplete server-side @@ -140,39 +144,43 @@ document.addEventListener("DOMContentLoaded", function () { let inputValue = document.getElementById(field).value; if (inputValue.length < 2) return; let districtValue = document.getElementById("district_name").value; // Ambil kecamatan terpilih - - let url = `${GlobalConfig.apiHost}/api/combobox/search-options?query=${encodeURIComponent(inputValue)}&field=${field}`; - + + let url = `${ + GlobalConfig.apiHost + }/api/combobox/search-options?query=${encodeURIComponent( + inputValue + )}&field=${field}`; + // Jika field desa, tambahkan kecamatan sebagai filter if (field === "village_name") { url += `&district=${encodeURIComponent(districtValue)}`; } - + fetch(url, { - method: 'GET', + method: "GET", headers: { Authorization: `Bearer ${document .querySelector('meta[name="api-token"]') .getAttribute("content")}`, "Content-Type": "application/json", - } + }, }) - .then(response => response.json()) - .then(data => { - let dataList = document.getElementById(field + "Options"); - dataList.innerHTML = ""; - - data.forEach(item => { - let option = document.createElement("option"); - option.value = item.name; - option.dataset.code = item.code; - dataList.appendChild(option); - }); - }) - .catch(error => console.error("Error fetching options:", error)); + .then((response) => response.json()) + .then((data) => { + let dataList = document.getElementById(field + "Options"); + dataList.innerHTML = ""; + + data.forEach((item) => { + let option = document.createElement("option"); + option.value = item.name; + option.dataset.code = item.code; + dataList.appendChild(option); + }); + }) + .catch((error) => console.error("Error fetching options:", error)); }; - document.querySelector('.btn-back').addEventListener('click', function() { + document.querySelector(".btn-back").addEventListener("click", function () { window.history.back(); }); -}); \ No newline at end of file +}); diff --git a/resources/js/data/advertisements/form-upload.js b/resources/js/data/advertisements/form-upload.js index ba6ec67..5c3ccd8 100644 --- a/resources/js/data/advertisements/form-upload.js +++ b/resources/js/data/advertisements/form-upload.js @@ -18,58 +18,61 @@ console.log(dropzonePreviewNode); url: `${GlobalConfig.apiHost}/api/advertisements/import`, // url: "https://httpbin.org/post", method: "post", - acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation + acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation previewTemplate: previewTemplate, previewsContainer: "#dropzone-preview", - autoProcessQueue: false, // Disable auto post + autoProcessQueue: false, // Disable auto post headers: { Authorization: `Bearer ${document .querySelector('meta[name="api-token"]') - .getAttribute("content")}` + .getAttribute("content")}`, }, - init: function() { + init: function () { // Listen for the success event - this.on("success", function(file, response) { + this.on("success", function (file, response) { console.log("File successfully uploaded:", file); console.log("API Response:", response); - + // Show success toast - showToast('bxs-check-square', 'green', response.message); - document.getElementById("submit-upload").innerHTML = "Upload Files"; + showToast("bxs-check-square", "green", response.message); + document.getElementById("submit-upload").innerHTML = + "Upload Files"; // Tunggu sebentar lalu reload halaman setTimeout(() => { - window.location.href = "/data/advertisements"; + window.location.href = "/data/advertisements/index"; }, 2000); }); // Listen for the error event - this.on("error", function(file, errorMessage) { + this.on("error", function (file, errorMessage) { console.error("Error uploading file:", file); console.error("Error message:", errorMessage); // Handle the error response // Show error toast - showToast('bxs-error-alt', 'red', errorMessage.message); - document.getElementById("submit-upload").innerHTML = "Upload Files"; + showToast("bxs-error-alt", "red", errorMessage.message); + document.getElementById("submit-upload").innerHTML = + "Upload Files"; }); - } + }, }))); // Add event listener to control the submission manually -document.querySelector("#submit-upload").addEventListener("click", function() { +document.querySelector("#submit-upload").addEventListener("click", function () { console.log("Ini adalah value dropzone", dropzone.files[0]); - const formData = new FormData() - console.log("Dropzonefiles",dropzone.files); + const formData = new FormData(); + console.log("Dropzonefiles", dropzone.files); - this.innerHTML = 'Loading...'; + this.innerHTML = + 'Loading...'; // Pastikan ada file dalam queue sebelum memprosesnya if (dropzone.files.length > 0) { - formData.append('file', dropzone.files[0]) + formData.append("file", dropzone.files[0]); console.log("ini adalah form data on submit", ...formData); - dropzone.processQueue(); // Ini akan manual memicu upload + dropzone.processQueue(); // Ini akan manual memicu upload } else { // Show error toast when no file is selected - showToast('bxs-error-alt', 'red', "Please add a file first."); + showToast("bxs-error-alt", "red", "Please add a file first."); document.getElementById("submit-upload").innerHTML = "Upload Files"; } }); @@ -82,62 +85,68 @@ dropzone.on("addedfile", function (file) { console.log("Ukuran File:", (file.size / 1024).toFixed(2) + " KB"); }); -dropzone.on("complete", function(file) { +dropzone.on("complete", function (file) { dropzone.removeFile(file); }); // Add event listener to donwload file template -document.getElementById('downloadtempadvertisement').addEventListener('click', function() { - var url = `${GlobalConfig.apiHost}/api/download-template-advertisement`; - fetch(url, { - method: 'GET', - headers: { - Authorization: `Bearer ${document - .querySelector('meta[name="api-token"]') - .getAttribute("content")}` - }, - }) - .then(response => { - if (response.ok) { - return response.blob(); - } else { - return response.json(); - } - }) - .then((blob) => { - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.style.display = 'none'; - a.href = url; - a.download = 'template_reklame.xlsx'; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - }) - .catch((error) => { - console.error("Gagal mendownload file:", error); - showToast('bxs-error-alt', 'red', "Template file is not already exist."); - }) -}) +document + .getElementById("downloadtempadvertisement") + .addEventListener("click", function () { + var url = `${GlobalConfig.apiHost}/api/download-template-advertisement`; + fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + }, + }) + .then((response) => { + if (response.ok) { + return response.blob(); + } else { + return response.json(); + } + }) + .then((blob) => { + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.style.display = "none"; + a.href = url; + a.download = "template_reklame.xlsx"; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + }) + .catch((error) => { + console.error("Gagal mendownload file:", error); + showToast( + "bxs-error-alt", + "red", + "Template file is not already exist." + ); + }); + }); // Function to show toast function showToast(iconClass, iconColor, message) { - const toastElement = document.getElementById('toastUploadAdvertisement'); - const toastBody = toastElement.querySelector('.toast-body'); - const toastHeader = toastElement.querySelector('.toast-header'); + const toastElement = document.getElementById("toastUploadAdvertisement"); + const toastBody = toastElement.querySelector(".toast-body"); + const toastHeader = toastElement.querySelector(".toast-header"); // Remove existing icon (if any) before adding the new one - const existingIcon = toastHeader.querySelector('.bx'); + const existingIcon = toastHeader.querySelector(".bx"); if (existingIcon) { - toastHeader.querySelector('.auth-logo').removeChild(existingIcon); // Remove the existing icon + toastHeader.querySelector(".auth-logo").removeChild(existingIcon); // Remove the existing icon } // Add the new icon to the toast header - const icon = document.createElement('i'); - icon.classList.add('bx', iconClass); - icon.style.fontSize = '25px'; + const icon = document.createElement("i"); + icon.classList.add("bx", iconClass); + icon.style.fontSize = "25px"; icon.style.color = iconColor; - toastHeader.querySelector('.auth-logo').appendChild(icon); + toastHeader.querySelector(".auth-logo").appendChild(icon); // Set the toast message toastBody.textContent = message; @@ -146,4 +155,3 @@ function showToast(iconClass, iconColor, message) { const toast = new bootstrap.Toast(toastElement); // Inisialisasi Bootstrap Toast toast.show(); } - diff --git a/routes/web.php b/routes/web.php index af26549..ea463b9 100755 --- a/routes/web.php +++ b/routes/web.php @@ -77,7 +77,9 @@ Route::group(['middleware' => 'auth'], function(){ // data Route::group(['prefix' => '/data'], function(){ // Resource route, kecuali create karena dibuat terpisah - Route::resource('/advertisements', AdvertisementController::class)->except(['create', 'show']); + Route::resource('/advertisements', AdvertisementController::class)->except(['create', 'show', 'index']); + + Route::get('/advertisements/index', [AdvertisementController::class, 'index'])->name('web.advertisements.index'); // Rute khusus untuk create dan bulk-create Route::get('/advertisements/create', [AdvertisementController::class, 'create'])->name('advertisements.create'); From 43a246d2346bf30ca1340c953b1ac1ab26a97eac Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 08:49:51 +0000 Subject: [PATCH 08/15] add permission deployment file --- deploy.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 deploy.sh diff --git a/deploy.sh b/deploy.sh old mode 100644 new mode 100755 From 8fcf8859d6a309f43eb874fb95399a6e811d146d Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 15:58:30 +0700 Subject: [PATCH 09/15] hot fix advertisement route conflict --- resources/js/data/advertisements/form-create-update.js | 2 +- resources/js/data/advertisements/form-upload.js | 2 +- routes/web.php | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/resources/js/data/advertisements/form-create-update.js b/resources/js/data/advertisements/form-create-update.js index 17a37ac..8a110da 100644 --- a/resources/js/data/advertisements/form-create-update.js +++ b/resources/js/data/advertisements/form-create-update.js @@ -73,7 +73,7 @@ document.addEventListener("DOMContentLoaded", function () { }, 2000); setTimeout(() => { - window.location.href = "/data/advertisements/index"; + window.location.href = "/data/web-advertisements"; }, 1000); } else { if (authLogo) { diff --git a/resources/js/data/advertisements/form-upload.js b/resources/js/data/advertisements/form-upload.js index 5c3ccd8..61f05a1 100644 --- a/resources/js/data/advertisements/form-upload.js +++ b/resources/js/data/advertisements/form-upload.js @@ -39,7 +39,7 @@ console.log(dropzonePreviewNode); "Upload Files"; // Tunggu sebentar lalu reload halaman setTimeout(() => { - window.location.href = "/data/advertisements/index"; + window.location.href = "/data/web-advertisements"; }, 2000); }); // Listen for the error event diff --git a/routes/web.php b/routes/web.php index ea463b9..befd164 100755 --- a/routes/web.php +++ b/routes/web.php @@ -77,9 +77,7 @@ Route::group(['middleware' => 'auth'], function(){ // data Route::group(['prefix' => '/data'], function(){ // Resource route, kecuali create karena dibuat terpisah - Route::resource('/advertisements', AdvertisementController::class)->except(['create', 'show', 'index']); - - Route::get('/advertisements/index', [AdvertisementController::class, 'index'])->name('web.advertisements.index'); + Route::resource('/web-advertisements', AdvertisementController::class)->except(['create', 'show']); // Rute khusus untuk create dan bulk-create Route::get('/advertisements/create', [AdvertisementController::class, 'create'])->name('advertisements.create'); From 435a19346bebf98ebbff2dc5af62ae70fce632db Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 16:05:21 +0700 Subject: [PATCH 10/15] fix route umkm --- app/Http/Controllers/Data/UmkmController.php | 2 +- database/seeders/UsersRoleMenuSeeder.php | 2 +- deploy.sh | 2 +- resources/js/data/umkm/form-create-update.js | 193 ++++++++++--------- resources/js/data/umkm/form-upload.js | 134 +++++++------ routes/web.php | 2 +- 6 files changed, 175 insertions(+), 160 deletions(-) diff --git a/app/Http/Controllers/Data/UmkmController.php b/app/Http/Controllers/Data/UmkmController.php index 6490c86..01bdef8 100644 --- a/app/Http/Controllers/Data/UmkmController.php +++ b/app/Http/Controllers/Data/UmkmController.php @@ -60,7 +60,7 @@ class UmkmController extends Controller $modelInstance = Umkm::find($id); // Pastikan model ditemukan if (!$modelInstance) { - return redirect()->route('umkm.index')->with('error', 'Umkm not found'); + return redirect()->route('web-umkm.index')->with('error', 'Umkm not found'); } // Mengambil dan memetakan village_name dan district_name diff --git a/database/seeders/UsersRoleMenuSeeder.php b/database/seeders/UsersRoleMenuSeeder.php index 02cb854..3239d52 100644 --- a/database/seeders/UsersRoleMenuSeeder.php +++ b/database/seeders/UsersRoleMenuSeeder.php @@ -181,7 +181,7 @@ class UsersRoleMenuSeeder extends Seeder ], [ "name" => "UMKM", - "url" => "umkm.index", + "url" => "web-umkm.index", "icon" => null, "parent_id" => $data->id, "sort_order" => 4, diff --git a/deploy.sh b/deploy.sh index 0c7b0f0..9a9f7d6 100755 --- a/deploy.sh +++ b/deploy.sh @@ -14,7 +14,7 @@ npm ci --no-audit --no-fund npm run build echo "📦 Installing composer dependencies..." -composer install --no-interaction --optimize-autoloader +COMPOSER_ALLOW_SUPERUSER=1 composer install --no-interaction --optimize-autoloader echo "🗄️ Running migrations..." php artisan migrate --force diff --git a/resources/js/data/umkm/form-create-update.js b/resources/js/data/umkm/form-create-update.js index d0ebd52..22dfe03 100644 --- a/resources/js/data/umkm/form-create-update.js +++ b/resources/js/data/umkm/form-create-update.js @@ -16,10 +16,10 @@ document.addEventListener("DOMContentLoaded", function () { Loading... `; const isEdit = saveButton.classList.contains("btn-edit"); - const formData = new FormData(form) - const toast = document.getElementById('toastEditUpdate'); - const toastBody = toast.querySelector('.toast-body'); - const toastHeader = toast.querySelector('.toast-header small'); + const formData = new FormData(form); + const toast = document.getElementById("toastEditUpdate"); + const toastBody = toast.querySelector(".toast-body"); + const toastHeader = toast.querySelector(".toast-header small"); const data = {}; @@ -40,53 +40,88 @@ document.addEventListener("DOMContentLoaded", function () { .querySelector('meta[name="api-token"]') .getAttribute("content")}`, "Content-Type": "application/json", - } + }, }) - .then(response => response.json()) - .then(data => { - console.log("Response data:", data); - if (!data.errors) { - // Remove existing icon (if any) before adding the new one - if (authLogo) { - // Hapus ikon yang sudah ada jika ada - const existingIcon = authLogo.querySelector('.bx'); - if (existingIcon) { - authLogo.removeChild(existingIcon); + .then((response) => response.json()) + .then((data) => { + console.log("Response data:", data); + if (!data.errors) { + // Remove existing icon (if any) before adding the new one + if (authLogo) { + // Hapus ikon yang sudah ada jika ada + const existingIcon = authLogo.querySelector(".bx"); + if (existingIcon) { + authLogo.removeChild(existingIcon); + } + + // Buat ikon baru + const icon = document.createElement("i"); + icon.classList.add("bx", "bxs-check-square"); + icon.style.fontSize = "25px"; + icon.style.color = "green"; // Pastikan 'green' dalam bentuk string + + // Tambahkan ikon ke dalam auth-logo + authLogo.appendChild(icon); } - // Buat ikon baru - const icon = document.createElement('i'); - icon.classList.add('bx', 'bxs-check-square'); - icon.style.fontSize = '25px'; - icon.style.color = 'green'; // Pastikan 'green' dalam bentuk string + // Set success message for the toast + toastBody.textContent = isEdit + ? "Data updated successfully!" + : "Data created successfully!"; + toast.classList.add("show"); // Show the toast + setTimeout(() => { + toast.classList.remove("show"); // Hide the toast after 3 seconds + }, 2000); - // Tambahkan ikon ke dalam auth-logo - authLogo.appendChild(icon); + setTimeout(() => { + window.location.href = "/data/web-umkm"; + }, 1000); + } else { + if (authLogo) { + // Hapus ikon yang sudah ada jika ada + const existingIcon = authLogo.querySelector(".bx"); + if (existingIcon) { + authLogo.removeChild(existingIcon); + } + + // Buat ikon baru + const icon = document.createElement("i"); + icon.classList.add("bx", "bxs-error-alt"); + icon.style.fontSize = "25px"; + icon.style.color = "red"; // Pastikan 'green' dalam bentuk string + + // Tambahkan ikon ke dalam auth-logo + authLogo.appendChild(icon); + } + + // Enable button and reset its text on error + modalButton.disabled = false; + modalButton.innerHTML = isEdit ? "Update" : "Create"; + + // Set error message for the toast + toastBody.textContent = + "Error: " + (data.message || "Something went wrong"); + toast.classList.add("show"); // Show the toast + + setTimeout(() => { + toast.classList.remove("show"); // Hide the toast after 3 seconds + }, 3000); } - - // Set success message for the toast - toastBody.textContent = isEdit ? "Data updated successfully!" : "Data created successfully!"; - toast.classList.add('show'); // Show the toast - setTimeout(() => { - toast.classList.remove('show'); // Hide the toast after 3 seconds - }, 2000); - - setTimeout(() => { - window.location.href = '/data/umkm'; - }, 1000); - } else { + }) + .catch((error) => { + console.error("Error:", error); if (authLogo) { // Hapus ikon yang sudah ada jika ada - const existingIcon = authLogo.querySelector('.bx'); + const existingIcon = authLogo.querySelector(".bx"); if (existingIcon) { authLogo.removeChild(existingIcon); } // Buat ikon baru - const icon = document.createElement('i'); - icon.classList.add('bx', 'bxs-error-alt'); - icon.style.fontSize = '25px'; - icon.style.color = 'red'; // Pastikan 'green' dalam bentuk string + const icon = document.createElement("i"); + icon.classList.add("bx", "bxs-error-alt"); + icon.style.fontSize = "25px"; + icon.style.color = "red"; // Pastikan 'green' dalam bentuk string // Tambahkan ikon ke dalam auth-logo authLogo.appendChild(icon); @@ -96,47 +131,15 @@ document.addEventListener("DOMContentLoaded", function () { modalButton.disabled = false; modalButton.innerHTML = isEdit ? "Update" : "Create"; - // Set error message for the toast - toastBody.textContent = "Error: " + (data.message || "Something went wrong"); - toast.classList.add('show'); // Show the toast + toastBody.textContent = + "An error occurred while processing your request."; + toast.classList.add("show"); // Show the toast setTimeout(() => { - toast.classList.remove('show'); // Hide the toast after 3 seconds + toast.classList.remove("show"); // Hide the toast after 3 seconds }, 3000); - } - }) - .catch(error => { - console.error("Error:", error); - if (authLogo) { - // Hapus ikon yang sudah ada jika ada - const existingIcon = authLogo.querySelector('.bx'); - if (existingIcon) { - authLogo.removeChild(existingIcon); - } - - // Buat ikon baru - const icon = document.createElement('i'); - icon.classList.add('bx', 'bxs-error-alt'); - icon.style.fontSize = '25px'; - icon.style.color = 'red'; // Pastikan 'green' dalam bentuk string - - // Tambahkan ikon ke dalam auth-logo - authLogo.appendChild(icon); - } - - // Enable button and reset its text on error - modalButton.disabled = false; - modalButton.innerHTML = isEdit ? "Update" : "Create"; - - // Set error message for the toast - toastBody.textContent = "An error occurred while processing your request."; - toast.classList.add('show'); // Show the toast - - setTimeout(() => { - toast.classList.remove('show'); // Hide the toast after 3 seconds - }, 3000); - }); + }); }); // Fungsi fetchOptions untuk autocomplete server-side @@ -145,7 +148,11 @@ document.addEventListener("DOMContentLoaded", function () { if (inputValue.length < 2) return; let districtValue = document.getElementById("district_name").value; // Ambil kecamatan terpilih - let url = `${GlobalConfig.apiHost}/api/combobox/search-options?query=${encodeURIComponent(inputValue)}&field=${field}`; + let url = `${ + GlobalConfig.apiHost + }/api/combobox/search-options?query=${encodeURIComponent( + inputValue + )}&field=${field}`; // Jika field desa, tambahkan kecamatan sebagai filter if (field === "village_name") { @@ -153,30 +160,30 @@ document.addEventListener("DOMContentLoaded", function () { } fetch(url, { - method: 'GET', + method: "GET", headers: { Authorization: `Bearer ${document .querySelector('meta[name="api-token"]') .getAttribute("content")}`, "Content-Type": "application/json", - } + }, }) - .then(response => response.json()) - .then(data => { - let dataList = document.getElementById(field + "Options"); - dataList.innerHTML = ""; + .then((response) => response.json()) + .then((data) => { + let dataList = document.getElementById(field + "Options"); + dataList.innerHTML = ""; - data.forEach(item => { - let option = document.createElement("option"); - option.value = item.name; - option.dataset.code = item.code; - dataList.appendChild(option); - }); - }) - .catch(error => console.error("Error fetching options:", error)); + data.forEach((item) => { + let option = document.createElement("option"); + option.value = item.name; + option.dataset.code = item.code; + dataList.appendChild(option); + }); + }) + .catch((error) => console.error("Error fetching options:", error)); }; - document.querySelector('.btn-back').addEventListener('click', function() { + document.querySelector(".btn-back").addEventListener("click", function () { window.history.back(); }); -}); \ No newline at end of file +}); diff --git a/resources/js/data/umkm/form-upload.js b/resources/js/data/umkm/form-upload.js index e1828e9..6290325 100644 --- a/resources/js/data/umkm/form-upload.js +++ b/resources/js/data/umkm/form-upload.js @@ -18,58 +18,61 @@ console.log(dropzonePreviewNode); url: `${GlobalConfig.apiHost}/api/umkm/import`, // url: "https://httpbin.org/post", method: "post", - acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation + acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation previewTemplate: previewTemplate, previewsContainer: "#dropzone-preview", - autoProcessQueue: false, // Disable auto post + autoProcessQueue: false, // Disable auto post headers: { Authorization: `Bearer ${document .querySelector('meta[name="api-token"]') - .getAttribute("content")}` + .getAttribute("content")}`, }, - init: function() { + init: function () { // Listen for the success event - this.on("success", function(file, response) { + this.on("success", function (file, response) { console.log("File successfully uploaded:", file); console.log("API Response:", response); - + // Show success toast - showToast('bxs-check-square', 'green', response.message); - document.getElementById("submit-upload").innerHTML = "Upload Files"; + showToast("bxs-check-square", "green", response.message); + document.getElementById("submit-upload").innerHTML = + "Upload Files"; // Tunggu sebentar lalu reload halaman setTimeout(() => { - window.location.href = "/data/umkm"; + window.location.href = "/data/web-umkm"; }, 2000); }); // Listen for the error event - this.on("error", function(file, errorMessage) { + this.on("error", function (file, errorMessage) { console.error("Error uploading file:", file); console.error("Error message:", errorMessage); // Handle the error response // Show error toast - showToast('bxs-error-alt', 'red', errorMessage.message); - document.getElementById("submit-upload").innerHTML = "Upload Files"; + showToast("bxs-error-alt", "red", errorMessage.message); + document.getElementById("submit-upload").innerHTML = + "Upload Files"; }); - } + }, }))); // Add event listener to control the submission manually -document.querySelector("#submit-upload").addEventListener("click", function() { +document.querySelector("#submit-upload").addEventListener("click", function () { console.log("Ini adalah value dropzone", dropzone.files[0]); - const formData = new FormData() - console.log("Dropzonefiles",dropzone.files); + const formData = new FormData(); + console.log("Dropzonefiles", dropzone.files); - this.innerHTML = 'Loading...'; + this.innerHTML = + 'Loading...'; // Pastikan ada file dalam queue sebelum memprosesnya if (dropzone.files.length > 0) { - formData.append('file', dropzone.files[0]) + formData.append("file", dropzone.files[0]); console.log("ini adalah form data on submit", ...formData); - dropzone.processQueue(); // Ini akan manual memicu upload + dropzone.processQueue(); // Ini akan manual memicu upload } else { // Show error toast when no file is selected - showToast('bxs-error-alt', 'red', "Please add a file first."); + showToast("bxs-error-alt", "red", "Please add a file first."); document.getElementById("submit-upload").innerHTML = "Upload Files"; } }); @@ -82,62 +85,68 @@ dropzone.on("addedfile", function (file) { console.log("Ukuran File:", (file.size / 1024).toFixed(2) + " KB"); }); -dropzone.on("complete", function(file) { +dropzone.on("complete", function (file) { dropzone.removeFile(file); }); // Add event listener to download file template -document.getElementById('downloadtempumkm').addEventListener('click', function() { - var url = `${GlobalConfig.apiHost}/api/download-template-umkm`; - fetch(url, { - method: 'GET', - headers: { - Authorization: `Bearer ${document - .querySelector('meta[name="api-token"]') - .getAttribute("content")}` - }, - }) - .then(response => { - if (response.ok) { - return response.blob(); // Jika respons OK, konversi menjadi blob - } else { - return response.json(); // Jika respons gagal, konversi menjadi JSON untuk menangani pesan error - } - }) - .then((blob) => { - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.style.display = 'none'; - a.href = url; - a.download = 'template_umkm.xlsx'; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - }) - .catch((error) => { - console.error("Gagal mendownload file:", error); - showToast('bxs-error-alt', 'red', "Template file is not already exist."); - }) -}) +document + .getElementById("downloadtempumkm") + .addEventListener("click", function () { + var url = `${GlobalConfig.apiHost}/api/download-template-umkm`; + fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + }, + }) + .then((response) => { + if (response.ok) { + return response.blob(); // Jika respons OK, konversi menjadi blob + } else { + return response.json(); // Jika respons gagal, konversi menjadi JSON untuk menangani pesan error + } + }) + .then((blob) => { + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.style.display = "none"; + a.href = url; + a.download = "template_umkm.xlsx"; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + }) + .catch((error) => { + console.error("Gagal mendownload file:", error); + showToast( + "bxs-error-alt", + "red", + "Template file is not already exist." + ); + }); + }); // Function to show toast function showToast(iconClass, iconColor, message) { - const toastElement = document.getElementById('toastUploadUmkm'); - const toastBody = toastElement.querySelector('.toast-body'); - const toastHeader = toastElement.querySelector('.toast-header'); + const toastElement = document.getElementById("toastUploadUmkm"); + const toastBody = toastElement.querySelector(".toast-body"); + const toastHeader = toastElement.querySelector(".toast-header"); // Remove existing icon (if any) before adding the new one - const existingIcon = toastHeader.querySelector('.bx'); + const existingIcon = toastHeader.querySelector(".bx"); if (existingIcon) { - toastHeader.querySelector('.auth-logo').removeChild(existingIcon); // Remove the existing icon + toastHeader.querySelector(".auth-logo").removeChild(existingIcon); // Remove the existing icon } // Add the new icon to the toast header - const icon = document.createElement('i'); - icon.classList.add('bx', iconClass); - icon.style.fontSize = '25px'; + const icon = document.createElement("i"); + icon.classList.add("bx", iconClass); + icon.style.fontSize = "25px"; icon.style.color = iconColor; - toastHeader.querySelector('.auth-logo').appendChild(icon); + toastHeader.querySelector(".auth-logo").appendChild(icon); // Set the toast message toastBody.textContent = message; @@ -146,4 +155,3 @@ function showToast(iconClass, iconColor, message) { const toast = new bootstrap.Toast(toastElement); // Inisialisasi Bootstrap Toast toast.show(); } - diff --git a/routes/web.php b/routes/web.php index befd164..8da0bfc 100755 --- a/routes/web.php +++ b/routes/web.php @@ -84,7 +84,7 @@ Route::group(['middleware' => 'auth'], function(){ Route::get('/advertisements/bulk-create', [AdvertisementController::class, 'bulkCreate'])->name('advertisements.bulk-create'); // Resource route, kecuali create karena dibuat terpisah - Route::resource('/umkm', UmkmController::class)->except(['create', 'show']); + Route::resource('/web-umkm', UmkmController::class)->except(['create', 'show']); // Rute khusus untuk create dan bulk-create Route::get('/umkm/create', [UmkmController::class, 'create'])->name('umkm.create'); From 0a7012a57c970eae6d3baa53460c3492bfb2f7a0 Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 16:13:17 +0700 Subject: [PATCH 11/15] fix tourisms routing --- .../Controllers/Data/TourismController.php | 2 +- database/seeders/UsersRoleMenuSeeder.php | 4 +- .../js/data/tourisms/form-create-update.js | 2 +- resources/js/data/tourisms/form-upload.js | 136 +++++++++--------- routes/web.php | 2 +- 5 files changed, 77 insertions(+), 69 deletions(-) diff --git a/app/Http/Controllers/Data/TourismController.php b/app/Http/Controllers/Data/TourismController.php index 87d4379..a36f11e 100644 --- a/app/Http/Controllers/Data/TourismController.php +++ b/app/Http/Controllers/Data/TourismController.php @@ -58,7 +58,7 @@ class TourismController extends Controller $modelInstance = Tourism::find($id); // Pastikan model ditemukan if (!$modelInstance) { - return redirect()->route('tourisms.index') ->with('error', 'Pariwisata tidak ditemukan'); + return redirect()->route('web-tourisms.index') ->with('error', 'Pariwisata tidak ditemukan'); } // Mengambil dan memetakan village_name dan district_name diff --git a/database/seeders/UsersRoleMenuSeeder.php b/database/seeders/UsersRoleMenuSeeder.php index 3239d52..6cdc016 100644 --- a/database/seeders/UsersRoleMenuSeeder.php +++ b/database/seeders/UsersRoleMenuSeeder.php @@ -188,7 +188,7 @@ class UsersRoleMenuSeeder extends Seeder ], [ "name" => "Pariwisata", - "url" => "tourisms.index", + "url" => "web-tourisms.index", "icon" => null, "parent_id" => $data->id, "sort_order" => 5, @@ -209,7 +209,7 @@ class UsersRoleMenuSeeder extends Seeder ], [ "name" => "Lap Pariwisata", - "url" => "tourisms.index", + "url" => "tourisms-report.index", "icon" => null, "parent_id" => $laporan->id, "sort_order" => 1, diff --git a/resources/js/data/tourisms/form-create-update.js b/resources/js/data/tourisms/form-create-update.js index c3d6971..806c47e 100644 --- a/resources/js/data/tourisms/form-create-update.js +++ b/resources/js/data/tourisms/form-create-update.js @@ -73,7 +73,7 @@ document.addEventListener("DOMContentLoaded", function () { }, 3000); setTimeout(() => { - window.location.href = "/data/tourisms"; + window.location.href = "/data/web-tourisms"; }, 3000); } else { if (authLogo) { diff --git a/resources/js/data/tourisms/form-upload.js b/resources/js/data/tourisms/form-upload.js index aab1366..d6bb6f8 100644 --- a/resources/js/data/tourisms/form-upload.js +++ b/resources/js/data/tourisms/form-upload.js @@ -18,58 +18,61 @@ console.log(dropzonePreviewNode); url: `${GlobalConfig.apiHost}/api/tourisms/import`, // url: "https://httpbin.org/post", method: "post", - acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation + acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation previewTemplate: previewTemplate, previewsContainer: "#dropzone-preview", - autoProcessQueue: false, // Disable auto post + autoProcessQueue: false, // Disable auto post headers: { Authorization: `Bearer ${document .querySelector('meta[name="api-token"]') - .getAttribute("content")}` + .getAttribute("content")}`, }, - init: function() { + init: function () { // Listen for the success event - this.on("success", function(file, response) { + this.on("success", function (file, response) { console.log("File successfully uploaded:", file); console.log("API Response:", response); - + // Show success toast - showToast('bxs-check-square', 'green', response.message); - document.getElementById("submit-upload").innerHTML = "Upload Files"; + showToast("bxs-check-square", "green", response.message); + document.getElementById("submit-upload").innerHTML = + "Upload Files"; // Tunggu sebentar lalu reload halaman setTimeout(() => { - window.location.href = "/data/tourisms"; + window.location.href = "/data/web-tourisms"; }, 2000); }); // Listen for the error event - this.on("error", function(file, errorMessage) { + this.on("error", function (file, errorMessage) { console.error("Error uploading file:", file); console.error("Error message:", errorMessage); // Handle the error response // Show error toast - showToast('bxs-error-alt', 'red', errorMessage.message); - document.getElementById("submit-upload").innerHTML = "Upload Files"; + showToast("bxs-error-alt", "red", errorMessage.message); + document.getElementById("submit-upload").innerHTML = + "Upload Files"; }); - } + }, }))); // Add event listener to control the submission manually -document.querySelector("#submit-upload").addEventListener("click", function() { +document.querySelector("#submit-upload").addEventListener("click", function () { console.log("Ini adalah value dropzone", dropzone.files[0]); - const formData = new FormData() - console.log("Dropzonefiles",dropzone.files); + const formData = new FormData(); + console.log("Dropzonefiles", dropzone.files); - this.innerHTML = 'Loading...'; + this.innerHTML = + 'Loading...'; // Pastikan ada file dalam queue sebelum memprosesnya if (dropzone.files.length > 0) { - formData.append('file', dropzone.files[0]) + formData.append("file", dropzone.files[0]); console.log("ini adalah form data on submit", ...formData); - dropzone.processQueue(); // Ini akan manual memicu upload + dropzone.processQueue(); // Ini akan manual memicu upload } else { // Show error toast when no file is selected - showToast('bxs-error-alt', 'red', "Please add a file first."); + showToast("bxs-error-alt", "red", "Please add a file first."); document.getElementById("submit-upload").innerHTML = "Upload Files"; } }); @@ -82,63 +85,69 @@ dropzone.on("addedfile", function (file) { console.log("Ukuran File:", (file.size / 1024).toFixed(2) + " KB"); }); -dropzone.on("complete", function(file) { +dropzone.on("complete", function (file) { dropzone.removeFile(file); }); // Add event listener to download file template -document.getElementById('downloadtemptourisms').addEventListener('click', function() { - var url = `${GlobalConfig.apiHost}/api/download-template-tourism`; - fetch(url, { - method: 'GET', - headers: { - Authorization: `Bearer ${document - .querySelector('meta[name="api-token"]') - .getAttribute("content")}` - }, - }) - .then(response => { - if (response.ok) { - return response.blob(); // Jika respons OK, konversi menjadi blob - } else { - return response.json(); // Jika respons gagal, konversi menjadi JSON untuk menangani pesan error - } - }) - .then((blob) => { - console.log(blob); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.style.display = 'none'; - a.href = url; - a.download = 'template_pariwisata.xlsx'; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - }) - .catch((error) => { - console.error("Gagal mendownload file:", error); - showToast('bxs-error-alt', 'red', "Template file is not already exist."); - }) -}) +document + .getElementById("downloadtemptourisms") + .addEventListener("click", function () { + var url = `${GlobalConfig.apiHost}/api/download-template-tourism`; + fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + }, + }) + .then((response) => { + if (response.ok) { + return response.blob(); // Jika respons OK, konversi menjadi blob + } else { + return response.json(); // Jika respons gagal, konversi menjadi JSON untuk menangani pesan error + } + }) + .then((blob) => { + console.log(blob); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.style.display = "none"; + a.href = url; + a.download = "template_pariwisata.xlsx"; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + }) + .catch((error) => { + console.error("Gagal mendownload file:", error); + showToast( + "bxs-error-alt", + "red", + "Template file is not already exist." + ); + }); + }); // Function to show toast function showToast(iconClass, iconColor, message) { - const toastElement = document.getElementById('toastUploadTourisms'); - const toastBody = toastElement.querySelector('.toast-body'); - const toastHeader = toastElement.querySelector('.toast-header'); + const toastElement = document.getElementById("toastUploadTourisms"); + const toastBody = toastElement.querySelector(".toast-body"); + const toastHeader = toastElement.querySelector(".toast-header"); // Remove existing icon (if any) before adding the new one - const existingIcon = toastHeader.querySelector('.bx'); + const existingIcon = toastHeader.querySelector(".bx"); if (existingIcon) { - toastHeader.querySelector('.auth-logo').removeChild(existingIcon); // Remove the existing icon + toastHeader.querySelector(".auth-logo").removeChild(existingIcon); // Remove the existing icon } // Add the new icon to the toast header - const icon = document.createElement('i'); - icon.classList.add('bx', iconClass); - icon.style.fontSize = '25px'; + const icon = document.createElement("i"); + icon.classList.add("bx", iconClass); + icon.style.fontSize = "25px"; icon.style.color = iconColor; - toastHeader.querySelector('.auth-logo').appendChild(icon); + toastHeader.querySelector(".auth-logo").appendChild(icon); // Set the toast message toastBody.textContent = message; @@ -147,4 +156,3 @@ function showToast(iconClass, iconColor, message) { const toast = new bootstrap.Toast(toastElement); // Inisialisasi Bootstrap Toast toast.show(); } - diff --git a/routes/web.php b/routes/web.php index 8da0bfc..7a74358 100755 --- a/routes/web.php +++ b/routes/web.php @@ -91,7 +91,7 @@ Route::group(['middleware' => 'auth'], function(){ Route::get('/umkm/bulk-create', [UmkmController::class, 'bulkCreate'])->name('umkm.bulk-create'); // Resource route, kecuali create karena dibuat terpisah - Route::resource('/tourisms', TourismController::class)->except(['create', 'show']); + Route::resource('/web-tourisms', TourismController::class)->except(['create', 'show']); // Rute khusus untuk create dan bulk-create Route::get('/tourisms/create', [TourismController::class, 'create'])->name('tourisms.create'); Route::get('/tourisms/bulk-create', [TourismController::class, 'bulkCreate'])->name('tourisms.bulk-create'); From 5b4780495ec1cee79929a77fdbdbce25a1740e74 Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 16:17:47 +0700 Subject: [PATCH 12/15] fix routing spatial-plannings --- database/seeders/UsersRoleMenuSeeder.php | 2 +- .../spatialPlannings/form-create-update.js | 2 +- .../js/data/spatialPlannings/form-upload.js | 136 +++++++++--------- routes/web.php | 2 +- 4 files changed, 75 insertions(+), 67 deletions(-) diff --git a/database/seeders/UsersRoleMenuSeeder.php b/database/seeders/UsersRoleMenuSeeder.php index 6cdc016..867922d 100644 --- a/database/seeders/UsersRoleMenuSeeder.php +++ b/database/seeders/UsersRoleMenuSeeder.php @@ -195,7 +195,7 @@ class UsersRoleMenuSeeder extends Seeder ], [ "name" => "Tata Ruang", - "url" => "spatial-plannings.index", + "url" => "web-spatial-plannings.index", "icon" => null, "parent_id" => $data->id, "sort_order" => 6, diff --git a/resources/js/data/spatialPlannings/form-create-update.js b/resources/js/data/spatialPlannings/form-create-update.js index f65687e..5ff395e 100644 --- a/resources/js/data/spatialPlannings/form-create-update.js +++ b/resources/js/data/spatialPlannings/form-create-update.js @@ -73,7 +73,7 @@ document.addEventListener("DOMContentLoaded", function () { }, 3000); setTimeout(() => { - window.location.href = "/data/spatial-plannings"; + window.location.href = "/data/web-spatial-plannings"; }, 3000); } else { if (authLogo) { diff --git a/resources/js/data/spatialPlannings/form-upload.js b/resources/js/data/spatialPlannings/form-upload.js index 4383f54..10bb5bc 100644 --- a/resources/js/data/spatialPlannings/form-upload.js +++ b/resources/js/data/spatialPlannings/form-upload.js @@ -18,58 +18,61 @@ console.log(dropzonePreviewNode); url: `${GlobalConfig.apiHost}/api/spatial-plannings/import`, // url: "https://httpbin.org/post", method: "post", - acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation + acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation previewTemplate: previewTemplate, previewsContainer: "#dropzone-preview", - autoProcessQueue: false, // Disable auto post + autoProcessQueue: false, // Disable auto post headers: { Authorization: `Bearer ${document .querySelector('meta[name="api-token"]') - .getAttribute("content")}` + .getAttribute("content")}`, }, - init: function() { + init: function () { // Listen for the success event - this.on("success", function(file, response) { + this.on("success", function (file, response) { console.log("File successfully uploaded:", file); console.log("API Response:", response); - + // Show success toast - showToast('bxs-check-square', 'green', response.message); - document.getElementById("submit-upload").innerHTML = "Upload Files"; + showToast("bxs-check-square", "green", response.message); + document.getElementById("submit-upload").innerHTML = + "Upload Files"; // Tunggu sebentar lalu reload halaman setTimeout(() => { - window.location.href = "/data/spatial-plannings"; + window.location.href = "/data/web-spatial-plannings"; }, 2000); }); // Listen for the error event - this.on("error", function(file, errorMessage) { + this.on("error", function (file, errorMessage) { console.error("Error uploading file:", file); console.error("Error message:", errorMessage); // Handle the error response // Show error toast - showToast('bxs-error-alt', 'red', errorMessage.message); - document.getElementById("submit-upload").innerHTML = "Upload Files"; + showToast("bxs-error-alt", "red", errorMessage.message); + document.getElementById("submit-upload").innerHTML = + "Upload Files"; }); - } + }, }))); // Add event listener to control the submission manually -document.querySelector("#submit-upload").addEventListener("click", function() { +document.querySelector("#submit-upload").addEventListener("click", function () { console.log("Ini adalah value dropzone", dropzone.files[0]); - const formData = new FormData() - console.log("Dropzonefiles",dropzone.files); + const formData = new FormData(); + console.log("Dropzonefiles", dropzone.files); - this.innerHTML = 'Loading...'; + this.innerHTML = + 'Loading...'; // Pastikan ada file dalam queue sebelum memprosesnya if (dropzone.files.length > 0) { - formData.append('file', dropzone.files[0]) + formData.append("file", dropzone.files[0]); console.log("ini adalah form data on submit", ...formData); - dropzone.processQueue(); // Ini akan manual memicu upload + dropzone.processQueue(); // Ini akan manual memicu upload } else { // Show error toast when no file is selected - showToast('bxs-error-alt', 'red', "Please add a file first."); + showToast("bxs-error-alt", "red", "Please add a file first."); document.getElementById("submit-upload").innerHTML = "Upload Files"; } }); @@ -82,63 +85,69 @@ dropzone.on("addedfile", function (file) { console.log("Ukuran File:", (file.size / 1024).toFixed(2) + " KB"); }); -dropzone.on("complete", function(file) { +dropzone.on("complete", function (file) { dropzone.removeFile(file); }); // Add event listener to download file template -document.getElementById('downloadtempspatialPlannings').addEventListener('click', function() { - var url = `${GlobalConfig.apiHost}/api/download-template-spatialPlannings`; - fetch(url, { - method: 'GET', - headers: { - Authorization: `Bearer ${document - .querySelector('meta[name="api-token"]') - .getAttribute("content")}` - }, - }) - .then(response => { - if (response.ok) { - return response.blob(); // Jika respons OK, konversi menjadi blob - } else { - return response.json(); // Jika respons gagal, konversi menjadi JSON untuk menangani pesan error - } - }) - .then((blob) => { - console.log(blob); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.style.display = 'none'; - a.href = url; - a.download = 'template_rencana_tata_ruang.xlsx'; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - }) - .catch((error) => { - console.error("Gagal mendownload file:", error); - showToast('bxs-error-alt', 'red', "Template file is not already exist."); - }) -}) +document + .getElementById("downloadtempspatialPlannings") + .addEventListener("click", function () { + var url = `${GlobalConfig.apiHost}/api/download-template-spatialPlannings`; + fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + }, + }) + .then((response) => { + if (response.ok) { + return response.blob(); // Jika respons OK, konversi menjadi blob + } else { + return response.json(); // Jika respons gagal, konversi menjadi JSON untuk menangani pesan error + } + }) + .then((blob) => { + console.log(blob); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.style.display = "none"; + a.href = url; + a.download = "template_rencana_tata_ruang.xlsx"; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + }) + .catch((error) => { + console.error("Gagal mendownload file:", error); + showToast( + "bxs-error-alt", + "red", + "Template file is not already exist." + ); + }); + }); // Function to show toast function showToast(iconClass, iconColor, message) { - const toastElement = document.getElementById('toastUploadSpatialPlannings'); - const toastBody = toastElement.querySelector('.toast-body'); - const toastHeader = toastElement.querySelector('.toast-header'); + const toastElement = document.getElementById("toastUploadSpatialPlannings"); + const toastBody = toastElement.querySelector(".toast-body"); + const toastHeader = toastElement.querySelector(".toast-header"); // Remove existing icon (if any) before adding the new one - const existingIcon = toastHeader.querySelector('.bx'); + const existingIcon = toastHeader.querySelector(".bx"); if (existingIcon) { - toastHeader.querySelector('.auth-logo').removeChild(existingIcon); // Remove the existing icon + toastHeader.querySelector(".auth-logo").removeChild(existingIcon); // Remove the existing icon } // Add the new icon to the toast header - const icon = document.createElement('i'); - icon.classList.add('bx', iconClass); - icon.style.fontSize = '25px'; + const icon = document.createElement("i"); + icon.classList.add("bx", iconClass); + icon.style.fontSize = "25px"; icon.style.color = iconColor; - toastHeader.querySelector('.auth-logo').appendChild(icon); + toastHeader.querySelector(".auth-logo").appendChild(icon); // Set the toast message toastBody.textContent = message; @@ -147,4 +156,3 @@ function showToast(iconClass, iconColor, message) { const toast = new bootstrap.Toast(toastElement); // Inisialisasi Bootstrap Toast toast.show(); } - diff --git a/routes/web.php b/routes/web.php index 7a74358..38a5813 100755 --- a/routes/web.php +++ b/routes/web.php @@ -97,7 +97,7 @@ Route::group(['middleware' => 'auth'], function(){ Route::get('/tourisms/bulk-create', [TourismController::class, 'bulkCreate'])->name('tourisms.bulk-create'); // Resource route, kecuali create karena dibuat terpisah - Route::resource('/spatial-plannings', SpatialPlanningController::class)->except(['create', 'show']); + Route::resource('/we-spatial-plannings', SpatialPlanningController::class)->except(['create', 'show']); // Rute khusus untuk create dan bulk-create Route::get('/spatial-plannings/create', [SpatialPlanningController::class, 'create'])->name('tourisms.create'); Route::get('/spatial-plannings/bulk-create', [SpatialPlanningController::class, 'bulkCreate'])->name('tourisms.bulk-create'); From 632433c49651ba4ba03fbb57edd52ff8ecf8fd45 Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 16:23:00 +0700 Subject: [PATCH 13/15] fix routing spatial-plannings --- routes/web.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/routes/web.php b/routes/web.php index 38a5813..c41ada7 100755 --- a/routes/web.php +++ b/routes/web.php @@ -97,10 +97,10 @@ Route::group(['middleware' => 'auth'], function(){ Route::get('/tourisms/bulk-create', [TourismController::class, 'bulkCreate'])->name('tourisms.bulk-create'); // Resource route, kecuali create karena dibuat terpisah - Route::resource('/we-spatial-plannings', SpatialPlanningController::class)->except(['create', 'show']); + Route::resource('/web-spatial-plannings', SpatialPlanningController::class)->except(['create', 'show']); // Rute khusus untuk create dan bulk-create - Route::get('/spatial-plannings/create', [SpatialPlanningController::class, 'create'])->name('tourisms.create'); - Route::get('/spatial-plannings/bulk-create', [SpatialPlanningController::class, 'bulkCreate'])->name('tourisms.bulk-create'); + Route::get('/spatial-plannings/create', [SpatialPlanningController::class, 'create'])->name('spatial-plannings.create'); + Route::get('/spatial-plannings/bulk-create', [SpatialPlanningController::class, 'bulkCreate'])->name('spatial-plannings.bulk-create'); Route::resource('/business-industries',BusinessOrIndustriesController::class); From a08f2cb2b7770f8ae1d5ab640af90bf7f65548ce Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 16:45:32 +0700 Subject: [PATCH 14/15] fix optimizing deployment --- deploy.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/deploy.sh b/deploy.sh index 9a9f7d6..33af4a3 100755 --- a/deploy.sh +++ b/deploy.sh @@ -20,11 +20,7 @@ echo "🗄️ Running migrations..." php artisan migrate --force echo "⚡ Optimizing application..." -php artisan cache:clear -php artisan config:clear -php artisan config:cache -php artisan route:cache -php artisan view:cache +php artisan optimize:clear echo "🔄 Restarting PHP service..." systemctl restart $PHP_VERSION-fpm From 86d694bcac5670157889793b0e4fd642adbf4052 Mon Sep 17 00:00:00 2001 From: arifal Date: Tue, 4 Mar 2025 17:56:30 +0700 Subject: [PATCH 15/15] add new menu chat bedas and view --- database/seeders/UsersRoleMenuSeeder.php | 18 +++++++++++++ database/view_query/v_advertisements.sql | 21 +++++++++++++++ database/view_query/v_tourism_base_kbli.sql | 8 ++++++ database/view_query/v_tourisms.sql | 29 +++++++++++++++++++++ database/view_query/v_umkms.sql | 28 ++++++++++++++++++++ 5 files changed, 104 insertions(+) create mode 100644 database/view_query/v_advertisements.sql create mode 100644 database/view_query/v_tourism_base_kbli.sql create mode 100644 database/view_query/v_tourisms.sql create mode 100644 database/view_query/v_umkms.sql diff --git a/database/seeders/UsersRoleMenuSeeder.php b/database/seeders/UsersRoleMenuSeeder.php index 867922d..f534ecb 100644 --- a/database/seeders/UsersRoleMenuSeeder.php +++ b/database/seeders/UsersRoleMenuSeeder.php @@ -74,6 +74,13 @@ class UsersRoleMenuSeeder extends Seeder "icon" => "mingcute:report-line", "parent_id" => null, "sort_order" => 6, + ], + [ + "name" => "Neng Bedas", + "url" => "/chat", + "icon" => "mingcute:wechat-line", + "parent_id" => null, + "sort_order" => 7, ] ]; @@ -92,6 +99,7 @@ class UsersRoleMenuSeeder extends Seeder $dataSettings = Menu::where('name', 'Data Settings')->first(); $data = Menu::where('name', 'Data')->first(); $laporan = Menu::where('name', 'Laporan')->first(); + $chat_bedas = Menu::where('name', 'Neng Bedas')->first(); // create children menu $children_menus = [ @@ -221,6 +229,13 @@ class UsersRoleMenuSeeder extends Seeder "parent_id" => $laporan->id, "sort_order" => 2, ], + [ + "name" => "Chat", + "url" => "main-chatbot.index", + "icon" => null, + "parent_id" => $chat_bedas->id, + "sort_order" => 1, + ], ]; foreach ($children_menus as $child_menu) { @@ -245,6 +260,7 @@ class UsersRoleMenuSeeder extends Seeder $pdam = Menu::where('name', 'PDAM')->first(); $peta = Menu::where('name', 'PETA')->first(); $bigdata_resume = Menu::where('name', 'Lap Pimpinan')->first(); + $chatbot = Menu::where('name', 'Chat')->first(); // Superadmin gets all menus $superadmin->menus()->sync([ @@ -255,6 +271,7 @@ class UsersRoleMenuSeeder extends Seeder $dataSettings->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false], $data->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false], $laporan->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false], + $chat_bedas->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false], // children $dashboard_pimpinan->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $dashboard_pbg->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], @@ -274,6 +291,7 @@ class UsersRoleMenuSeeder extends Seeder $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], $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], ]); // Admin gets limited menus diff --git a/database/view_query/v_advertisements.sql b/database/view_query/v_advertisements.sql new file mode 100644 index 0000000..24c808a --- /dev/null +++ b/database/view_query/v_advertisements.sql @@ -0,0 +1,21 @@ +CREATE VIEW v_advertisements AS +SELECT + a.no, + a.business_name, + a.npwpd, + a.advertisement_type, + a.advertisement_content, + a.business_address, + a.advertisement_location, + v.village_name AS village_name, + d.district_name AS district_name, + a.length, + a.width, + a.viewing_angle, + a.face, + a.area, + a.angle, + a.contact +FROM advertisements a +JOIN villages v ON a.village_code = v.village_code +JOIN districts d ON a.district_code = d.district_code; \ No newline at end of file diff --git a/database/view_query/v_tourism_base_kbli.sql b/database/view_query/v_tourism_base_kbli.sql new file mode 100644 index 0000000..ba9377f --- /dev/null +++ b/database/view_query/v_tourism_base_kbli.sql @@ -0,0 +1,8 @@ +CREATE VIEW v_tourisms_based_kbli AS +SELECT kbli_title, total_records +FROM ( + SELECT kbli, kbli_title, COUNT(*) AS total_records + FROM tourisms + GROUP BY kbli, kbli_title +) AS subquery +ORDER BY total_records DESC; \ No newline at end of file diff --git a/database/view_query/v_tourisms.sql b/database/view_query/v_tourisms.sql new file mode 100644 index 0000000..4033535 --- /dev/null +++ b/database/view_query/v_tourisms.sql @@ -0,0 +1,29 @@ +CREATE VIEW v_tourisms AS +SELECT + t.project_id, + t.project_type_id, + t.nib, + t.business_name, + t.oss_publication_date, + t.investment_status_description, + t.business_form, + t.project_risk, + t.project_name, + t.business_scale, + t.business_address, + v.village_name as village_name, + d.district_name as district_name, + t.longitude, + t.latitude, + t.project_submission_date, + t.kbli_title, + t.supervisory_sector, + t.user_name, + t.email, + t.contact, + t.land_area_in_m2, + t.investment_amount, + t.tki +FROM tourisms t +JOIN villages v on t.village_code = v.village_code +JOIN districts d on t.district_code = d.district_code; \ No newline at end of file diff --git a/database/view_query/v_umkms.sql b/database/view_query/v_umkms.sql new file mode 100644 index 0000000..534e08b --- /dev/null +++ b/database/view_query/v_umkms.sql @@ -0,0 +1,28 @@ +CREATE VIEW v_umkms AS +SELECT + u.business_address, + u.business_contact, + u.business_desc, + bf.business_form, + u.business_id_number, + u.business_name, + bs.business_scale, + u.business_type, + u.created_at, + d.district_name, + u.land_area, + u.number_of_employee, + u.owner_address, + u.owner_contact, + u.owner_id, + u.owner_name, + ps.permit_status, + u.revenue, + u.updated_at, + v.village_name +FROM umkms u +JOIN business_form bf on u.business_form_id = bf.id +JOIN permit_status ps on u.permit_status_id = ps.id +JOIn business_scale bs on u.business_scale_id = bs.id +JOIN villages v on u.village_code = v.village_code +JOIN districts d on u.district_code = v.district_code; \ No newline at end of file