Compare commits

...

20 Commits

Author SHA1 Message Date
arifal
c5e3fdd915 create file automation deployment using sh 2025-03-04 15:05:13 +07:00
arifal
cdd84d02da fix parsing separator 2025-03-04 14:30:41 +07:00
arifal
ee1a395c75 add per page 50 and add some column onn laporan pimpinan 2025-03-04 10:41:52 +07:00
arifal
3bfcaddba4 fix queue execute scraping syncronize simbg add new column to resume 2025-03-03 22:55:57 +07:00
arifal
9ea7e96af1 add vite and merge chatbot jamal 2025-02-28 18:39:23 +07:00
arifal
e0d11af7d2 Merge remote-tracking branch 'origin/feature/chatbot' into dev 2025-02-28 18:14:29 +07:00
@jamaludinarifrohman6661
fefef609ac feature: chatbot 2025-02-28 17:53:16 +07:00
arifal
f5790cda94 fix filter year dashboard 2025-02-28 15:46:31 +07:00
arifal
f3db3783f9 deployed 2025-02-28 2025-02-28 04:49:28 +00:00
arifal
101e76c0fa add js to vite 2025-02-28 11:45:55 +07:00
arifal
4cc698a623 fix handle disable button when upload business industries 2025-02-28 10:37:41 +07:00
arifal
544ad1db46 fix upload business and industry with chunk 2025-02-28 00:35:09 +07:00
arifal
30ca819aa1 fix color on maps 2025-02-27 20:14:37 +07:00
arifal
b0bab784d1 partial update add page laporan pimpinan and fix upload big data excel pdam 2025-02-27 19:23:06 +07:00
arifal
01fda22c89 partial update add loading on maps and add color for zone type maps 2025-02-26 23:52:48 +07:00
arifal
de300c2c32 fix dashboard style and resume bigdata 2025-02-26 21:52:23 +07:00
arifal
7f8a2e4936 Merge branch 'feature/dashboard-pbg' into dev 2025-02-25 11:34:08 +07:00
@jamaludinarifrohman6661
38948b6633 bug-fix: map tourisms 2025-02-25 11:32:39 +07:00
arifal
aa9943ba45 hot fix vite config js resources 2025-02-25 04:18:15 +00:00
arifal
9e55ea0dbb fix change api route data settings 2025-02-25 10:41:59 +07:00
71 changed files with 5427 additions and 4028 deletions

View File

@@ -1,66 +1,37 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p> # Usage icon
<p align="center"> search or pick icon in <a href="https://icon-sets.iconify.design/mingcute/?keyword=mingcute">here</a>
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel # Set up queue for running automatically
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: - Install Supervisor
- [Simple, fast routing engine](https://laravel.com/docs/routing). ```
- [Powerful dependency injection container](https://laravel.com/docs/container). sudo apt update && sudo apt install supervisor -y
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. ```
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications. - Create Supervisor Config
## Learning Laravel ```
sudo nano /etc/supervisor/conf.d/laravel-worker.conf
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. [program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /path-to-your-project/artisan queue:work --tries=3 --timeout=600
autostart=true
autorestart=true
numprocs=1
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/laravel-worker.log
```
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. - Reload Supervisor
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. ```
sudo supervisorctl reread
## Laravel Sponsors sudo supervisorctl update
sudo supervisorctl start laravel-worker
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). sudo supervisorctl restart laravel-worker
sudo supervisorctl status
### Premium Partners ```
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[WebReinvent](https://webreinvent.com/)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
- **[Cyber-Duck](https://cyber-duck.co.uk)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Jump24](https://jump24.co.uk)**
- **[Redberry](https://redberry.international/laravel/)**
- **[Active Logic](https://activelogic.com)**
- **[byte5](https://byte5.de)**
- **[OP.GG](https://op.gg)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View File

@@ -2,6 +2,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Jobs\SyncronizeSIMBG;
use App\Services\ServiceSIMBG; use App\Services\ServiceSIMBG;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use \Illuminate\Support\Facades\Log; use \Illuminate\Support\Facades\Log;
@@ -28,13 +29,12 @@ class ExecuteScraping extends Command
private $service_simbg; private $service_simbg;
public function __construct(ServiceSIMBG $service_simbg){ public function __construct(){
$this->service_simbg = $service_simbg;
parent::__construct(); parent::__construct();
} }
public function handle() public function handle()
{ {
SyncronizeSIMBG::dispatch();
Log::info("running scheduler daily scraping"); Log::info("running scheduler daily scraping");
$this->service_simbg->syncTaskList();
} }
} }

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Resources\BigdataResumeResource;
use App\Models\BigdataResume; use App\Models\BigdataResume;
use App\Models\DataSetting; use App\Models\DataSetting;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -18,11 +19,12 @@ class BigDataResumeController extends Controller
try{ try{
$filterDate = $request->get("filterByDate"); $filterDate = $request->get("filterByDate");
// If filterByDate is "latest" or empty, get the most recent record
if (!$filterDate || $filterDate === "latest") { if (!$filterDate || $filterDate === "latest") {
$big_data_resume = BigdataResume::latest()->first(); $big_data_resume = BigdataResume::where('year', now()->year)->latest()->first();
if (!$big_data_resume) {
return $this->response_empty_resume();
}
} else { } else {
// Filter by specific date
$big_data_resume = BigdataResume::whereDate('created_at', $filterDate) $big_data_resume = BigdataResume::whereDate('created_at', $filterDate)
->orderBy('id', 'desc') ->orderBy('id', 'desc')
->first(); ->first();
@@ -37,15 +39,19 @@ class BigDataResumeController extends Controller
return response()->json(['message' => 'No data setting found']); return response()->json(['message' => 'No data setting found']);
} }
$target_pad = floatval(optional($data_settings->where('key', 'TARGET_PAD')->first())->value); function cleanNumber($value) {
$tata_ruang = floatval(optional($data_settings->where('key', 'TATA_RUANG')->first())->value); return floatval(str_replace('.', '', $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);
$target_pad = floatval(optional($data_settings->where('key', 'TARGET_PAD')->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; $kekurangan_potensi = $target_pad - $big_data_resume->potention_sum;
// percentage kekurangan potensi // percentage kekurangan potensi
@@ -81,8 +87,8 @@ class BigDataResumeController extends Controller
? round(($realisasi_terbit_pbg_sum / $big_data_resume->verified_sum) * 100, 2) : 0; ? round(($realisasi_terbit_pbg_sum / $big_data_resume->verified_sum) * 100, 2) : 0;
// percentage menunggu klik dpmptsp // percentage menunggu klik dpmptsp
$menunggu_klik_dpmptsp_percentage = $big_data_resume->verified_sum > 0 && $menuggu_klik_dpmptsp_sum > 0 $menunggu_klik_dpmptsp_percentage = $big_data_resume->verified_sum > 0 && $menunggu_klik_dpmptsp_sum > 0
? round(($menuggu_klik_dpmptsp_sum / $big_data_resume->verified_sum) * 100, 2) : 0; ? round(($menunggu_klik_dpmptsp_sum / $big_data_resume->verified_sum) * 100, 2) : 0;
// percentage proses_dinas_teknis // percentage proses_dinas_teknis
$proses_dinas_teknis_percentage = $big_data_resume->verified_sum > 0 && $proses_dinas_teknis_sum > 0 $proses_dinas_teknis_percentage = $big_data_resume->verified_sum > 0 && $proses_dinas_teknis_sum > 0
@@ -94,7 +100,8 @@ class BigDataResumeController extends Controller
'percentage' => 100, 'percentage' => 100,
], ],
'tata_ruang' => [ 'tata_ruang' => [
'sum' => $tata_ruang, 'sum' => $big_data_resume->spatial_sum,
'count' => $big_data_resume->spatial_count,
'percentage' => $tata_ruang_percentage, 'percentage' => $tata_ruang_percentage,
], ],
'kekurangan_potensi' => [ 'kekurangan_potensi' => [
@@ -132,8 +139,8 @@ class BigDataResumeController extends Controller
'percentage' => $realisasi_terbit_percentage 'percentage' => $realisasi_terbit_percentage
], ],
'menunggu_klik_dpmptsp' => [ 'menunggu_klik_dpmptsp' => [
'sum' => $menuggu_klik_dpmptsp_sum, 'sum' => $menunggu_klik_dpmptsp_sum,
'count' => $menuggu_klik_dpmptsp_count, 'count' => $menunggu_klik_dpmptsp_count,
'percentage' => $menunggu_klik_dpmptsp_percentage 'percentage' => $menunggu_klik_dpmptsp_percentage
], ],
'proses_dinas_teknis' => [ 'proses_dinas_teknis' => [
@@ -148,36 +155,22 @@ class BigDataResumeController extends Controller
} }
} }
/**
* Store a newly created resource in storage.
*/ public function bigdata_report(Request $request){
public function store(Request $request) try{
{ $query = BigdataResume::query()->orderBy('id', 'desc');
//
if($request->filled('search')){
$query->where('name', 'LIKE', '%'.$request->input('search').'%');
} }
/** $query = $query->paginate(config('app.paginate_per_page', 50));
* Display the specified resource. return BigdataResumeResource::collection($query)->response()->getData(true);
*/ }catch(\Exception $e){
public function show(string $id) Log::error($e->getMessage());
{ return response()->json(['message' => 'Error when fetching data'], 500);
//
} }
/**
* 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(){ private function response_empty_resume(){

View File

@@ -9,6 +9,7 @@ use App\Models\BusinessOrIndustry;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
use \Illuminate\Support\Facades\Validator; use \Illuminate\Support\Facades\Validator;
use App\Http\Requests\ExcelUploadRequest;
class BusinessOrIndustriesController extends Controller class BusinessOrIndustriesController extends Controller
{ {
/** /**
@@ -30,7 +31,7 @@ class BusinessOrIndustriesController extends Controller
}); });
} }
return response()->json($query->paginate()); return response()->json($query->paginate(config('app.paginate_per_page', 50)));
} }
/** /**
@@ -79,29 +80,15 @@ class BusinessOrIndustriesController extends Controller
} }
} }
public function upload(Request $request){ public function upload(ExcelUploadRequest $request){
try {
if ($request->hasFile('file')) { if(!$request->hasFile('file')){
$file = $request->file('file');
}
// Validasi file
$validator = Validator::make($request->all(), [
'file' => 'required|mimes:xlsx,xls|max:102400', // Max 100MB
]);
if ($validator->fails()) {
return response()->json([ return response()->json([
'message' => 'File validation failed.', 'error' => 'No file provided'
'errors' => $validator->errors()
], 400); ], 400);
} }
try {
// Ambil file dari request
$file = $request->file('file'); $file = $request->file('file');
// Menggunakan Laravel Excel untuk mengimpor file
Excel::import(new BusinessIndustriesImport, $file); Excel::import(new BusinessIndustriesImport, $file);
// Jika sukses, kembalikan respons sukses // Jika sukses, kembalikan respons sukses

View File

@@ -0,0 +1,84 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Services\OpenAIService;
use App\Http\Controllers\Controller;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
class ChatbotController extends Controller
{
protected $openAIService;
public function __construct(OpenAIService $openAIService)
{
$this->openAIService = $openAIService;
}
public function generateText(Request $request)
{
info($request);
$request->validate([
'tab_active' => 'required|string',
'prompt' => 'required|string',
]);
$tab_active = $request->input('tab_active');
$main_content = match ($tab_active) {
"count-retribusi" => "RETRIBUTION",
"document-validation" => "DOCUMENT VALIDATION",
"data-information" => "DATA SUMMARY",
default => "UNKNOWN",
};
if ($main_content === "UNKNOWN") {
return response()->json(['response' => 'Invalid tab_active value.'], 400);
}
info($main_content);
// Klasifikasi apakah pertanyaan butuh database atau bisa dijawab langsung
$classifyResponse = $this->openAIService->generateClassifyMainContent($request->input('prompt'), $main_content);
if ($classifyResponse === "DATABASE") {
$queryResponse = $this->openAIService->generateQueryBasedMainContent($request->input('prompt'), $main_content);
if (is_array($queryResponse)) {
info('Query Response is an array: ', $queryResponse);
} else {
info('Query Response is a string: ' . $queryResponse);
}
// Validasi query dua kali sebelum eksekusi
if (
$this->openAIService->validateSyntaxQuery($queryResponse) === "VALID" &&
$this->openAIService->validateSyntaxQuery($queryResponse) === "VALID"
) {
info($queryResponse);
$queryResponse = str_replace(['```sql', '```'], '', $queryResponse);
$resultQuery = DB::select($queryResponse);
$formattedResultQuery = json_encode($resultQuery, JSON_PRETTY_PRINT);
$nlpResult = $this->openAIService->generateNLPFromQuery($request->input('prompt'), $formattedResultQuery);
$finalGeneratedText =$this->openAIService->generateFinalText($nlpResult);
return response()->json(['response' => $finalGeneratedText]);
}
return response()->json(['response' => ''], 400);
}
if ($classifyResponse === "GENERAL") {
$nlpResult = $this->openAIService->generateGeneralText($request->input('prompt'), $main_content);
$finalGeneratedText =$this->openAIService->generateFinalText($nlpResult);
return response()->json(['response' => $finalGeneratedText]);
}
return response()->json(['response' => ''], 500);
}
private function classifyContent(string $prompt) {
$classifyResponse = $this->openAIService->generateClassifyContent($prompt);
return $classifyResponse;
}
}

View File

@@ -22,9 +22,9 @@ class CustomersController extends Controller
if ($request->has('search') &&!empty($request->get('search'))) { if ($request->has('search') &&!empty($request->get('search'))) {
$query = $query->where('nomor_pelanggan', 'LIKE', '%'.$request->get('search').'%') $query = $query->where('nomor_pelanggan', 'LIKE', '%'.$request->get('search').'%')
->orWhere('nama', 'LIKE', '%'.$request->get('search').'%') ->orWhere('nama', 'LIKE', '%'.$request->get('search').'%')
->orWhere('kota_palayanan', '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)));
} }
/** /**

View File

@@ -24,7 +24,7 @@ class ImportDatasourceController extends Controller
$search = $request->get("search"); $search = $request->get("search");
$query->where('status', 'like', "%".$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(){ public function checkImportDatasource(){

View File

@@ -7,6 +7,7 @@ use App\Models\Advertisement;
use App\Models\Customer; use App\Models\Customer;
use App\Models\SpatialPlanning; use App\Models\SpatialPlanning;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\TourismBasedKBLI;
class LackOfPotentialController extends Controller class LackOfPotentialController extends Controller
{ {
@@ -16,11 +17,13 @@ class LackOfPotentialController extends Controller
$total_reklame = Advertisement::count(); $total_reklame = Advertisement::count();
$total_pdam = Customer::count(); $total_pdam = Customer::count();
$total_tata_ruang = SpatialPlanning::count(); $total_tata_ruang = SpatialPlanning::count();
$data_report_tourism = TourismBasedKBLI::all();
return response()->json([ return response()->json([
'total_reklame' => $total_reklame, 'total_reklame' => $total_reklame,
'total_pdam' => $total_pdam, 'total_pdam' => $total_pdam,
'total_tata_ruang' => $total_tata_ruang 'total_tata_ruang' => $total_tata_ruang,
'data_report' => $data_report_tourism,
], 200); ], 200);
}catch(\Exception $e){ }catch(\Exception $e){
return response()->json([ return response()->json([

View File

@@ -22,7 +22,7 @@ class MenusController extends Controller
$query = $query->where("name", "like", "%".$request->get("search")."%"); $query = $query->where("name", "like", "%".$request->get("search")."%");
} }
return response()->json($query->paginate()); return response()->json($query->paginate(config('app.paginate_per_page', 50)));
} }
/** /**

View File

@@ -17,7 +17,8 @@ class RequestAssignmentController extends Controller
$query = PbgTask::query()->orderBy('id', 'desc'); $query = PbgTask::query()->orderBy('id', 'desc');
if($request->has('search') && !empty($request->get("search"))){ if($request->has('search') && !empty($request->get("search"))){
$query->where('name', 'LIKE', '%'.$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()); return RequestAssignmentResouce::collection($query->paginate());
} }

View File

@@ -20,7 +20,7 @@ class RolesController extends Controller
$query = $query->where('name', 'like', '%'. $request->get('search') . '%'); $query = $query->where('name', 'like', '%'. $request->get('search') . '%');
} }
return response()->json($query->paginate()); return response()->json($query->paginate(config('app.paginate_per_page', 50)));
} }
/** /**

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Enums\ImportDatasourceStatus; use App\Enums\ImportDatasourceStatus;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Jobs\SyncronizeSIMBG;
use App\Models\ImportDatasource; use App\Models\ImportDatasource;
use App\Traits\GlobalApiResponse; use App\Traits\GlobalApiResponse;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
@@ -23,8 +24,8 @@ class ScrapingController extends Controller
} }
// run service artisan command // run service artisan command
Artisan::call("app:execute-scraping"); SyncronizeSIMBG::dispatch();
return $this->resSuccess("Success execute scraping service please wait"); return $this->resSuccess(["message" => "Success execute scraping service on background, check status for more"]);
} }
/** /**

View File

@@ -31,7 +31,7 @@ class UsersController extends Controller
if($request->has('search') && !empty($request->get("search"))){ if($request->has('search') && !empty($request->get("search"))){
$query->where('name', 'LIKE', '%'.$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){ public function logout(Request $request){
$request->user()->tokens()->delete(); $request->user()->tokens()->delete();

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class BigdataResumesController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return view('bigdata-resumes.index');
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Controllers\Chatbot;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ChatbotController extends Controller
{
/**
* Displya a listing of the resource
*/
public function index()
{
return view('chatbot.index');
}
}

View File

@@ -17,12 +17,12 @@ class SyncronizeController extends Controller
} }
public function syncPbgTask(){ public function syncPbgTask(){
$res = $this->service_simbg->syncTaskList(); $res = $this->service_simbg->syncTaskPBG();
return $res; return $res;
} }
public function syncronizeTask(Request $request){ public function syncronizeTask(Request $request){
$res = $this->service_simbg->syncTaskList(); $res = $this->service_simbg->syncTaskPBG();
return redirect()->back()->with('success', 'Processing completed successfully'); return redirect()->back()->with('success', 'Processing completed successfully');
} }

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class BigdataResumeResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'import_datasource_id' => $this->import_datasource_id,
'potention_count' => (int) $this->potention_count,
'potention_sum' => number_format((float) $this->potention_sum, 2, ',', '.'),
'non_verified_count' => (int) $this->non_verified_count,
'non_verified_sum' => number_format((float) $this->non_verified_sum, 2, ',', '.'),
'verified_count' => (int) $this->verified_count,
'verified_sum' => number_format((float) $this->verified_sum, 2, ',', '.'),
'business_count' => (int) $this->business_count,
'business_sum' => number_format((float) $this->business_sum, 2, ',', '.'),
'non_business_count' => (int) $this->non_business_count,
'non_business_sum' => number_format((float) $this->non_business_sum, 2, ',', '.'),
'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(),
];
}
}

View File

@@ -3,37 +3,105 @@
namespace App\Imports; namespace App\Imports;
use App\Models\BusinessOrIndustry; use App\Models\BusinessOrIndustry;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\ToCollection;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithChunkReading;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
use Illuminate\Contracts\Queue\ShouldQueue;
use Maatwebsite\Excel\Concerns\WithBatchInserts;
use Illuminate\Support\Facades\Log;
class BusinessIndustriesImport implements ToCollection class BusinessIndustriesImport implements ToCollection, WithMultipleSheets, WithChunkReading, WithBatchInserts, ShouldQueue, WithHeadingRow
{ {
/** /**
* @param array $row * @param Collection $collection
*
* @return \Illuminate\Database\Eloquent\Model|null
*/ */
public function collection(Collection $rows) public function collection(Collection $collection)
{ {
foreach ($rows->skip(1) as $row){ try{
$clean_nop = preg_replace('/[^A-Za-z0-9]/', '', $row[2]); $batchData = [];
if (!BusinessOrIndustry::where('nop', $clean_nop)->exists()) { $batchSize = 1000;
BusinessOrIndustry::create([
'nama_kecamatan' => $row[0], foreach ($collection as $row){
'nama_kelurahan' => $row[1], if(!isset($row['nop']) || empty($row['nop'])){
'nop' => $clean_nop, // Store cleaned 'nop' continue;
'nama_wajib_pajak' => $row[3], }
'alamat_wajib_pajak' => $row[4],
'alamat_objek_pajak' => $row[5],
'luas_bumi' => $row[6], $clean_nop = preg_replace('/[^A-Za-z0-9]/', '', $row['nop']);
'luas_bangunan' => $row[7],
'njop_bumi' => $row[8], $batchData[] = [
'njop_bangunan' => $row[9], 'nama_kecamatan' => $row['nama_kecamatan'],
'ketetapan' => $row[10], 'nama_kelurahan' => $row['nama_kelurahan'],
'tahun_pajak' => $row[11], 'nop' => $clean_nop,
'nama_wajib_pajak' => $row['nama_wajib_pajak'],
'alamat_wajib_pajak' => $row['alamat_wajib_pajak'],
'alamat_objek_pajak' => $row['alamat_objek_pajak'],
'luas_bumi' => $row['luas_bumi'],
'luas_bangunan' => $row['luas_bangunan'],
'njop_bumi' => $row['njop_bumi'],
'njop_bangunan' => $row['njop_bangunan'],
'ketetapan' => $row['ketetapan'],
'tahun_pajak' => $row['tahun_pajak'],
];
if(count($batchData) >= $batchSize){
BusinessOrIndustry::upsert($batchData, ['nop'], [
'nama_kecamatan',
'nama_kelurahan',
'nama_wajib_pajak',
'alamat_wajib_pajak',
'alamat_objek_pajak',
'luas_bumi',
'luas_bangunan',
'njop_bumi',
'njop_bangunan',
'ketetapan',
'tahun_pajak',
]);
$batchData = [];
}
}
if(!empty($batchData)){
BusinessOrIndustry::upsert($batchData, ['nop'], [
'nama_kecamatan',
'nama_kelurahan',
'nama_wajib_pajak',
'alamat_wajib_pajak',
'alamat_objek_pajak',
'luas_bumi',
'luas_bangunan',
'njop_bumi',
'njop_bangunan',
'ketetapan',
'tahun_pajak',
]); ]);
} }
}catch(\Exception $exception){
Log::error('Error while importing Business Industries data:', ['error' => $exception->getMessage()]);
return;
} }
} }
public function sheets(): array {
return [
0 => $this
];
}
public function headingRow(): int
{
return 1;
}
public function chunkSize(): int
{
return 1000;
}
public function batchSize(): int
{
return 1000;
}
} }

View File

@@ -11,8 +11,9 @@ use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithMultipleSheets; use Maatwebsite\Excel\Concerns\WithMultipleSheets;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Maatwebsite\Excel\Concerns\WithBatchInserts; use Maatwebsite\Excel\Concerns\WithBatchInserts;
use Illuminate\Support\Facades\Log;
class CustomersImport implements ToCollection, WithMultipleSheets class CustomersImport implements ToCollection, WithMultipleSheets, WithChunkReading, WithBatchInserts, ShouldQueue, WithHeadingRow
{ {
/** /**
* @param Collection $collection * @param Collection $collection
@@ -20,23 +21,51 @@ class CustomersImport implements ToCollection, WithMultipleSheets
public function collection(Collection $collection) public function collection(Collection $collection)
{ {
$batchData = []; $batchData = [];
$batchSize = 1000;
foreach ($collection->skip(1) as $row) { foreach ($collection as $row) {
if (!isset($row[0]) || empty($row[0])) { if (!isset($row['nomor_pelanggan']) || empty($row['nomor_pelanggan'])) {
continue; continue;
} }
$latitude = filter_var($row[4], FILTER_VALIDATE_FLOAT) ? bcadd($row[4], '0', 18) : null; $latitude = '0';
$longitude = filter_var($row[5], FILTER_VALIDATE_FLOAT) ? bcadd($row[5], '0', 18) : null; $longitude = '0';
if (isset($row['latkor']) && !empty(trim($row['latkor']))) {
$latitude = str_replace(',', '.', trim($row['latkor']));
if (is_numeric($latitude)) {
$latitude = bcadd($latitude, '0', 18);
} else {
$latitude = '0';
}
} else {
$latitude = '0';
}
if (isset($row['lonkor']) && !empty(trim($row['lonkor']))) {
$longitude = str_replace(',', '.', trim($row['lonkor']));
if (is_numeric($longitude)) {
$longitude = bcadd($longitude, '0', 18);
} else {
$longitude = '0';
}
} else {
$longitude = '0';
}
$batchData[] = [ $batchData[] = [
'nomor_pelanggan' => $row[0], 'nomor_pelanggan' => $row['nomor_pelanggan'] ?? '',
'kota_pelayanan' => $row[1], 'kota_pelayanan' => $row['kota_pelayanan'] ?? '',
'nama' => $row[2], 'nama' => $row['nama'] ?? '',
'alamat' => $row[3], 'alamat' => $row['alamat'] ?? '',
'latitude' => $latitude, 'latitude' => $latitude,
'longitude' => $longitude, 'longitude' => $longitude,
]; ];
if (count($batchData) >= $batchSize) {
Customer::upsert($batchData, ['nomor_pelanggan'], ['kota_pelayanan', 'nama', 'alamat', 'latitude', 'longitude']);
$batchData = [];
}
} }
if (!empty($batchData)) { if (!empty($batchData)) {
@@ -44,9 +73,20 @@ class CustomersImport implements ToCollection, WithMultipleSheets
} }
} }
public function sheets(): array { public function sheets(): array {
return [ return [
0 => $this 0 => $this
]; ];
} }
public function chunkSize(): int
{
return 1000;
}
public function batchSize(): int
{
return 1000;
}
} }

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Jobs;
use App\Services\GoogleSheetService;
use App\Services\ServiceSIMBG;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SyncronizeSIMBG implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct()
{
}
public function handle(): void
{
$serviceSIMBG = app(ServiceSIMBG::class);
$serviceSIMBG->syncTaskPBG();
}
}

View File

@@ -21,6 +21,15 @@ class BigdataResume extends Model
'business_sum', 'business_sum',
'non_business_count', 'non_business_count',
'non_business_sum', 'non_business_sum',
'spatial_count',
'spatial_sum',
'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() public function importDatasource()
@@ -28,88 +37,84 @@ class BigdataResume extends Model
return $this->belongsTo(ImportDatasource::class, 'import_datasource_id'); return $this->belongsTo(ImportDatasource::class, 'import_datasource_id');
} }
public static function generateResumeData($import_datasource_id){ public static function generateResumeData($import_datasource_id, $year, $data_setting){
$query_verified = once( function () { $stats = PbgTask::with(['googleSheet', 'pbg_task_retributions'])
return DB::table('pbg_task AS pt') ->leftJoin('pbg_task_retributions as ptr', 'pbg_task.uuid', '=', 'ptr.pbg_task_uid')
->leftJoin('pbg_task_google_sheet AS ptgs', 'pt.registration_number', '=', 'ptgs.no_registrasi') ->leftJoin('pbg_task_google_sheet as ptgs', 'pbg_task.registration_number', '=', 'ptgs.no_registrasi')
->leftJoin('pbg_task_retributions AS ptr', 'pt.uuid', '=', 'ptr.pbg_task_uid') ->when($year !== 'all', function ($query) use ($year) {
->whereRaw('LOWER(TRIM(ptgs.status_verifikasi)) = ?', [strtolower(trim('Selesai Verifikasi'))]) $query->whereYear('pbg_task.task_created_at', (int) $year);
->selectRaw('COUNT(pt.id) AS total_data, })
SUM(ptr.nilai_retribusi_bangunan) AS total_retribution') ->selectRaw("
COUNT(CASE WHEN LOWER(TRIM(ptgs.status_verifikasi)) = 'selesai verifikasi' THEN 1 END) AS verified_count,
SUM(CASE WHEN LOWER(TRIM(ptgs.status_verifikasi)) = 'selesai verifikasi' THEN ptr.nilai_retribusi_bangunan ELSE 0 END) AS verified_total,
COUNT(CASE WHEN LOWER(TRIM(ptgs.status_verifikasi)) != 'selesai verifikasi' OR ptgs.status_verifikasi IS NULL THEN 1 END) AS non_verified_count,
SUM(CASE WHEN LOWER(TRIM(ptgs.status_verifikasi)) != 'selesai verifikasi' OR ptgs.status_verifikasi IS NULL THEN ptr.nilai_retribusi_bangunan ELSE 0 END) AS non_verified_total,
COUNT(CASE WHEN (LOWER(TRIM(ptgs.status_verifikasi)) != 'selesai verifikasi' OR ptgs.status_verifikasi IS NULL)
AND LOWER(TRIM(pbg_task.function_type)) = 'sebagai tempat usaha' THEN 1 END) AS business_count,
SUM(CASE WHEN (LOWER(TRIM(ptgs.status_verifikasi)) != 'selesai verifikasi' OR ptgs.status_verifikasi IS NULL)
AND LOWER(TRIM(pbg_task.function_type)) = 'sebagai tempat usaha' THEN ptr.nilai_retribusi_bangunan ELSE 0 END) AS business_total,
COUNT(CASE WHEN (LOWER(TRIM(ptgs.status_verifikasi)) != 'selesai verifikasi' OR ptgs.status_verifikasi IS NULL)
AND (LOWER(TRIM(pbg_task.function_type)) != 'sebagai tempat usaha' OR pbg_task.function_type IS NULL) THEN 1 END) AS non_business_count,
SUM(CASE WHEN (LOWER(TRIM(ptgs.status_verifikasi)) != 'selesai verifikasi' OR ptgs.status_verifikasi IS NULL)
AND (LOWER(TRIM(pbg_task.function_type)) != 'sebagai tempat usaha' OR pbg_task.function_type IS NULL) THEN ptr.nilai_retribusi_bangunan ELSE 0 END) AS non_business_total
")
->first(); ->first();
// Assign Results
$verified_count = $stats->verified_count ?? 0;
$verified_total = $stats->verified_total ?? 0;
$non_verified_count = $stats->non_verified_count ?? 0;
$non_verified_total = $stats->non_verified_total ?? 0;
$business_count = $stats->business_count ?? 0;
$business_total = $stats->business_total ?? 0;
$non_business_count = $stats->non_business_count ?? 0;
$non_business_total = $stats->non_business_total ?? 0;
$query_potention = once(function () use ($year) {
$query = PbgTask::leftJoin('pbg_task_retributions as ptr', 'pbg_task.uuid', '=', 'ptr.pbg_task_uid')
->selectRaw('COUNT(DISTINCT pbg_task.id) as task_count, SUM(ptr.nilai_retribusi_bangunan) as total_retribution');
if ($year !== 'all') {
$query->whereYear('pbg_task.task_created_at', (int) $year);
}
return $query->first();
}); });
$verified_count = $query_verified->total_data ?? 0;
$verified_total = $query_verified->total_retribution ?? 0;
$query_business = once(function () {
return DB::table('pbg_task AS pt')
->leftJoin('pbg_task_google_sheet AS ptgs', 'pt.registration_number', '=', 'ptgs.no_registrasi')
->leftJoin('pbg_task_retributions AS ptr', 'pt.uuid', '=', 'ptr.pbg_task_uid')
->where(function ($query) {
$query->whereRaw('LOWER(TRIM(ptgs.status_verifikasi)) != ?', [strtolower(trim('Selesai Verifikasi'))])
->orWhereNull('ptgs.status_verifikasi');
})
->where(function ($query) {
$query->whereRaw('LOWER(TRIM(pt.function_type)) = ?', [strtolower(trim('Sebagai Tempat Usaha'))]);
})
->selectRaw('COUNT(pt.id) AS total_data,
SUM(ptr.nilai_retribusi_bangunan) AS total_retribution')
->first();
});
$business_count = $query_business->total_data ?? 0;
$business_total = $query_business->total_retribution ?? 0;
$query_non_business = once( function () {
return DB::table('pbg_task AS pt')
->leftJoin('pbg_task_google_sheet AS ptgs', 'pt.registration_number', '=', 'ptgs.no_registrasi')
->leftJoin('pbg_task_retributions AS ptr', 'pt.uuid', '=', 'ptr.pbg_task_uid') // Join ke pbg_task_retributions
->where(function ($query) {
$query->whereRaw('LOWER(TRIM(ptgs.status_verifikasi)) != ?', [strtolower(trim('Selesai Verifikasi'))])
->orWhereNull('ptgs.status_verifikasi'); // Include NULL values
})
->where(function ($query) {
$query->whereRaw('LOWER(TRIM(pt.function_type)) != ?', [strtolower(trim('Sebagai Tempat Usaha'))])
->orWhereNull('pt.function_type'); // Include NULL values
})
->selectRaw('COUNT(pt.id) AS total_data,
SUM(ptr.nilai_retribusi_bangunan) AS total_retribution') // Menambahkan SUM dari pbg_task_retributions
->first();
});
$non_business_count = $query_non_business->total_data ?? 0;
$non_business_total = $query_non_business->total_retribution ?? 0;
$query_non_verified = once(function () {
return DB::table('pbg_task AS pt')
->leftJoin('pbg_task_google_sheet AS ptgs', 'pt.registration_number', '=', 'ptgs.no_registrasi')
->leftJoin('pbg_task_retributions AS ptr', 'pt.uuid', '=', 'ptr.pbg_task_uid') // Join tabel pbg_task_retributions
->where(function ($query) {
$query->whereRaw('LOWER(TRIM(ptgs.status_verifikasi)) != ?', [strtolower(trim('Selesai Verifikasi'))])
->orWhereNull('ptgs.status_verifikasi'); // Include NULL values
})
->selectRaw('COUNT(pt.id) AS total_data,
SUM(ptr.nilai_retribusi_bangunan) AS total_retribution') // Menambahkan SUM dari pbg_task_retributions
->first();
});
$non_verified_count = $query_non_verified->total_data ?? 0;
$non_verified_total = $query_non_verified->total_retribution ?? 0;
$query_potention = once( function () {
return DB::table('pbg_task as pt')
->leftJoin('pbg_task_retributions as ptr', 'pt.uuid', '=', 'ptr.pbg_task_uid')
->select(
DB::raw('COUNT(DISTINCT pt.id) as task_count'),
DB::raw('SUM(ptr.nilai_retribusi_bangunan) as total_retribution')
)
->first();
});
$potention_count = $query_potention->task_count ?? 0; $potention_count = $query_potention->task_count ?? 0;
$potention_total = $query_potention->total_retribution ?? 0; $potention_total = $query_potention->total_retribution ?? 0;
$query_spatial_plannings = once(function () use ($year) {
$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);
}
return $query->first();
});
$spatial_planning_count = $query_spatial_plannings->task_count ?? 0;
$spatial_planning_total = $query_spatial_plannings->total_retribution;
$potention_count -= $spatial_planning_count;
$potention_total -= $spatial_planning_total;
return self::create([ return self::create([
'import_datasource_id' => $import_datasource_id, 'import_datasource_id' => $import_datasource_id,
'spatial_count' => $spatial_planning_count,
'spatial_sum' => $spatial_planning_total ?? 0.00,
'potention_count' => $potention_count ?? 0, 'potention_count' => $potention_count ?? 0,
'potention_sum' => $potention_total ?? 0.00, 'potention_sum' => $potention_total ?? 0.00,
'non_verified_count' => $non_verified_count ?? 0, 'non_verified_count' => $non_verified_count ?? 0,
@@ -120,6 +125,13 @@ class BigdataResume extends Model
'business_sum' => $business_total ?? 0.00, 'business_sum' => $business_total ?? 0.00,
'non_business_count' => $non_business_count ?? 0, 'non_business_count' => $non_business_count ?? 0,
'non_business_sum' => $non_business_total ?? 0.00, 'non_business_sum' => $non_business_total ?? 0.00,
'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,
]); ]);
} }
} }

View File

@@ -37,4 +37,8 @@ class PbgTask extends Model
public function pbg_task_index_integrations(){ public function pbg_task_index_integrations(){
return $this->hasOne(PbgTaskIndexIntegrations::class, 'pbg_task_uid', 'uuid'); return $this->hasOne(PbgTaskIndexIntegrations::class, 'pbg_task_uid', 'uuid');
} }
public function googleSheet(){
return $this->hasOne(PbgTaskGoogleSheet::class, 'no_registrasi', 'registration_number');
}
} }

View File

@@ -9,6 +9,8 @@ use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Carbon\Carbon; use Carbon\Carbon;
use App\Services\ServiceSIMBG;
use App\Services\GoogleSheetService;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@@ -17,7 +19,9 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function register(): void public function register(): void
{ {
// $this->app->singleton(ServiceSIMBG::class, function ($app) {
return new ServiceSIMBG($app->make(GoogleSheetService::class));
});
} }
/** /**

View File

@@ -13,6 +13,7 @@ class GoogleSheetService
protected $client; protected $client;
protected $service; protected $service;
protected $spreadsheetID; protected $spreadsheetID;
protected $service_sheets;
public function __construct() public function __construct()
{ {
$this->client = new Google_Client(); $this->client = new Google_Client();

View File

@@ -0,0 +1,157 @@
<?php
namespace App\Services;
use OpenAI;
use Illuminate\Support\Facades\Storage;
class OpenAIService
{
protected $client;
public function __construct()
{
$this->client = OpenAI::client(env('OPENAI_API_KEY'));
}
public function generateGeneralText($prompt, $mainContent)
{
$response = $this->client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
[
'role' => 'system',
'content' => "You are an expert assistant. Your task is to generate a concise response based on the provided prompt and main content.
Guidelines:
- Summarize the key points in exactly 5 bullet points.
- Ensure the response is clear and relevant to the prompt.
- Use simple and professional language."
],
['role' => 'user', 'content' => "Prompt: $prompt \nMain Content: $mainContent"],
],
]);
return trim($response['choices'][0]['message']['content'] ?? 'No response');
}
public function generateClassifyMainContent($prompt, $mainContent)
{
$response = $this->client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
[
'role' => 'system',
'content' => "You are an expert assistant in classifying questions based on whether their answers must be retrieved from a database or can be explained generally.
Your task is to return one of the following two labels:
- \"DATABASE\" → If the question requires specific data that can only be obtained from a database.
- \"GENERAL\" → If the question can be answered without accessing a database.
Consider the following context: \"$mainContent\"
Respond with only one of the labels: \"DATABASE\" or \"GENERAL\"."
],
['role' => 'user', 'content' => $prompt],
],
]);
return trim($response['choices'][0]['message']['content'] ?? 'No response');
}
public function generateQueryBasedMainContent($prompt, $mainContent)
{
// Load file JSON
$jsonPath = public_path('templates/contentTemplatePrompt.json'); // Sesuaikan path
$jsonData = json_decode(file_get_contents($jsonPath), true);
// Periksa apakah kategori ada dalam JSON
if (!isset($jsonData[$mainContent])) {
return "Template prompt tidak ditemukan.";
}
// Ambil template berdasarkan kategori
$promptTemplate = $jsonData[$mainContent]['prompt'];
$response = $this->client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'system', 'content' => $promptTemplate],
['role' => 'user', 'content' => $prompt],
],
]);
return trim($response['choices'][0]['message']['content'] ?? 'No response');
}
public function validateSyntaxQuery($queryResponse)
{
$response = $this->client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
[
'role' => 'system',
'content' => "You are a MariaDB SQL expert. Your task is to validate the syntax of an SQL query to ensure it follows proper MariaDB syntax rules.
Guidelines:
- Check for any syntax errors, missing keywords, or incorrect clause usage.
- Ensure the query is well-structured and adheres to best practices.
- Verify that all SQL keywords are used correctly and in the right order.
- If the query is valid, respond with: \"VALID\".
- If the query has issues, respond with: \"INVALID\".
Always respond with either \"VALID\" or \"INVALID\"."
],
['role' => 'user', 'content' => $queryResponse],
],
]);
return trim($response['choices'][0]['message']['content'] ?? 'No response');
}
public function generateNLPFromQuery($inputUser, $resultQuery) {
$response = $this->client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
[
'role' => 'system',
'content' => "You are an expert assistant. Your task is to analyze the database query results and transform them into a human-readable answer based on the user's question.
Guidelines:
- Understand the user's question and extract the key intent.
- Summarize or format the query results to directly answer the user's question.
- Ensure the response is clear, concise, and relevant.
- If the query result is empty or does not match the question, provide a polite response indicating that no data is available.
Always provide a well-structured response that makes sense based on the input question."
],
['role' => 'user', 'content' => "User's question: $inputUser \nDatabase result: $resultQuery"],
],
]);
return trim($response['choices'][0]['message']['content'] ?? 'No response');
}
public function generateFinalText($nlpResult) {
$response = $this->client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
[
'role' => 'system',
'content' => "You are an expert text formatter. Your task is to take the given NLP result and format it into a structured, human-readable text suitable for rendering inside an HTML <div>.
Guidelines:
- Preserve the meaning and clarity of the content.
- Use proper line breaks for readability.
- If the text contains lists, convert them into bullet points.
- Emphasize important keywords using <strong> tags if necessary.
- Ensure the response remains clean and concise without extra explanations."
],
['role' => 'user', 'content' => "Here is the NLP result that needs formatting:\n\n$nlpResult"],
],
]);
return trim($response['choices'][0]['message']['content'] ?? 'No response');
}
}

View File

@@ -15,6 +15,9 @@ use App\Traits\GlobalApiResponse;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Carbon\Carbon; use Carbon\Carbon;
use App\Services\ServiceClient; use App\Services\ServiceClient;
use App\Services\GoogleSheetService;
use App\Models\DataSetting;
use App\Models\PbgTaskGoogleSheet;
class ServiceSIMBG class ServiceSIMBG
{ {
@@ -24,10 +27,11 @@ class ServiceSIMBG
private $simbg_host; private $simbg_host;
private $fetch_per_page; private $fetch_per_page;
private $service_client; private $service_client;
private $googleSheetService;
/** /**
* Create a new class instance. * Create a new class instance.
*/ */
public function __construct() public function __construct(GoogleSheetService $googleSheetService)
{ {
$settings = GlobalSetting::whereIn('key', [ $settings = GlobalSetting::whereIn('key', [
'SIMBG_EMAIL', 'SIMBG_PASSWORD', 'SIMBG_HOST', 'FETCH_PER_PAGE' '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->fetch_per_page = trim((string) ($settings['FETCH_PER_PAGE'] ?? ""));
$this->service_client = new ServiceClient($this->simbg_host); $this->service_client = new ServiceClient($this->simbg_host);
$this->googleSheetService = $googleSheetService;
} }
public function getToken(){ public function getToken(){
@@ -81,13 +86,13 @@ class ServiceSIMBG
if (empty($res->original['success']) || !$res->original['success']) { if (empty($res->original['success']) || !$res->original['success']) {
// Log error // Log error
Log::error("API response indicates failure", ['url' => $url, 'uuid' => $uuid]); Log::error("API response indicates failure", ['url' => $url, 'uuid' => $uuid]);
return false; continue;
} }
$data = $res->original['data']['data'] ?? null; $data = $res->original['data']['data'] ?? null;
if (!$data) { if (!$data) {
Log::error("No valid data returned from API", ['url' => $url, 'uuid' => $uuid]); Log::error("No valid data returned from API", ['url' => $url, 'uuid' => $uuid]);
return false; continue;
} }
$integrations[] = [ $integrations[] = [
@@ -105,19 +110,6 @@ class ServiceSIMBG
PbgTaskIndexIntegrations::upsert($integrations, ['pbg_task_uid'], ['indeks_fungsi_bangunan', PbgTaskIndexIntegrations::upsert($integrations, ['pbg_task_uid'], ['indeks_fungsi_bangunan',
'indeks_parameter_kompleksitas', 'indeks_parameter_permanensi', 'indeks_parameter_ketinggian', 'faktor_kepemilikan', 'indeks_terintegrasi', 'total']); 'indeks_parameter_kompleksitas', 'indeks_parameter_permanensi', 'indeks_parameter_ketinggian', 'faktor_kepemilikan', 'indeks_terintegrasi', 'total']);
// $resultData = PbgTaskIndexIntegrations::updateOrCreate(
// ['pbg_task_uid' => $uuid],
// [
// 'indeks_fungsi_bangunan' => $data['indeks_fungsi_bangunan'] ?? null,
// 'indeks_parameter_kompleksitas' => $data['indeks_parameter_kompleksitas'] ?? null,
// 'indeks_parameter_permanensi' => $data['indeks_parameter_permanensi'] ?? null,
// 'indeks_parameter_ketinggian' => $data['indeks_parameter_ketinggian'] ?? null,
// 'faktor_kepemilikan' => $data['faktor_kepemilikan'] ?? null,
// 'indeks_terintegrasi' => $data['indeks_terintegrasi'] ?? null,
// 'total' => $data['total'] ?? null,
// ]
// );
return true; return true;
}catch (Exception $e){ }catch (Exception $e){
Log::error('error when sync index integration ', ['index integration'=> $e->getMessage()]); Log::error('error when sync index integration ', ['index integration'=> $e->getMessage()]);
@@ -125,14 +117,175 @@ class ServiceSIMBG
} }
} }
public function syncTaskList() public function syncTaskPBG()
{ {
$initResToken = $this->getToken(); try {
$importDatasource = ImportDatasource::create([ $importDatasource = ImportDatasource::create([
'status' => ImportDatasourceStatus::Processing->value, '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'])) { if (empty($initResToken->original['data']['token']['access'])) {
$importDatasource->update([ $importDatasource->update([
'status' => ImportDatasourceStatus::Failed->value, 'status' => ImportDatasourceStatus::Failed->value,
@@ -140,7 +293,6 @@ class ServiceSIMBG
]); ]);
return $this->resError("Failed to retrieve token"); return $this->resError("Failed to retrieve token");
} }
$apiToken = $initResToken->original['data']['token']['access']; $apiToken = $initResToken->original['data']['token']['access'];
$headers = ['Authorization' => "Bearer " . $apiToken]; $headers = ['Authorization' => "Bearer " . $apiToken];
@@ -159,32 +311,19 @@ class ServiceSIMBG
$savedCount = $failedCount = 0; $savedCount = $failedCount = 0;
for ($currentPage = 1; $currentPage <= $totalPage; $currentPage++) { for ($currentPage = 1; $currentPage <= $totalPage; $currentPage++) {
try {
$pageUrl = "/api/pbg/v1/list/?page={$currentPage}&size={$this->fetch_per_page}&sort=ASC"; $pageUrl = "/api/pbg/v1/list/?page={$currentPage}&size={$this->fetch_per_page}&sort=ASC";
$getToken = $this->getToken();
Log::info("response index integration", ['currentPage' => $currentPage]); Log::info("Fetching tasks", ['currentPage' => $currentPage]);
if (empty($getToken->original['data']['token']['access'])) {
$importDatasource->update([
'status' => ImportDatasourceStatus::Failed->value,
'message' => 'Failed to retrieve token'
]);
break;
}
$token = $getToken->original['data']['token']['access'];
$headers = ['Authorization' => "Bearer " . $token];
$response = $this->service_client->get($pageUrl, $headers); $response = $this->service_client->get($pageUrl, $headers);
$tasks = $response->original['data']['data'] ?? []; $tasks = $response->original['data']['data'] ?? [];
if (empty($tasks)) { if (empty($tasks)) {
$importDatasource->update([
'status' => ImportDatasourceStatus::Failed->value,
'message' => 'No data found on page'
]);
Log::warning("No data found on page", ['page' => $currentPage]); Log::warning("No data found on page", ['page' => $currentPage]);
break; continue;
} }
Log::info("executed page", ['page' => $currentPage, 'total' => $totalPage]);
$tasksCollective = []; $tasksCollective = [];
foreach ($tasks as $item) { foreach ($tasks as $item) {
try { try {
@@ -211,25 +350,19 @@ class ServiceSIMBG
'created_at' => now(), 'created_at' => now(),
]; ];
// $this->syncIndexIntegration($item['uid'], $token); $this->syncTaskDetailSubmit($item['uid'], $apiToken);
$this->syncTaskDetailSubmit($item['uid'], $token);
$savedCount++; $savedCount++;
} catch (Exception $e) { } catch (Exception $e) {
$failedCount++; $failedCount++;
$importDatasource->update([
'status' => ImportDatasourceStatus::Failed->value,
'message' => "Successfully processed: $savedCount, Failed: $failedCount"
]);
Log::error("Failed to process task", [ Log::error("Failed to process task", [
'error' => $e->getMessage(), 'error' => $e->getMessage(),
'task' => $item, 'task' => $item,
]); ]);
break; continue; // Skip failed task, continue processing the rest
} }
} }
if (!empty($tasksCollective)) {
PbgTask::upsert($tasksCollective, ['uuid'], [ PbgTask::upsert($tasksCollective, ['uuid'], [
'name', 'owner_name', 'application_type', 'application_type_name', 'condition', 'name', 'owner_name', 'application_type', 'application_type_name', 'condition',
'registration_number', 'document_number', 'address', 'status', 'status_name', 'registration_number', 'document_number', 'address', 'status', 'status_name',
@@ -238,20 +371,42 @@ class ServiceSIMBG
]); ]);
$uuids = array_column($tasksCollective, 'uuid'); $uuids = array_column($tasksCollective, 'uuid');
$this->syncIndexIntegration($uuids, $token); $this->syncIndexIntegration($uuids, $apiToken);
}
} catch (Exception $e) {
Log::error("Failed to process page", [
'error' => $e->getMessage(),
'page' => $currentPage,
]);
continue; // Skip the failed page and move to the next
}
} }
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([ $importDatasource->update([
'status' => ImportDatasourceStatus::Success->value, 'status' => ImportDatasourceStatus::Success->value,
'message' => "Successfully processed: $savedCount, Failed: $failedCount" 'message' => "Successfully processed: $savedCount, Failed: $failedCount"
]); ]);
BigdataResume::generateResumeData($importDatasource->id);
Log::info("syncTaskList completed", ['savedCount' => $savedCount, 'failedCount' => $failedCount]); Log::info("syncTaskList completed", ['savedCount' => $savedCount, 'failedCount' => $failedCount]);
return $this->resSuccess(['savedCount' => $savedCount, 'failedCount' => $failedCount]); return $this->resSuccess(['savedCount' => $savedCount, 'failedCount' => $failedCount]);
} catch (Exception $e) {
Log::error("syncTaskList failed", ['error' => $e->getMessage()]);
if (isset($importDatasource)) {
$importDatasource->update([
'status' => ImportDatasourceStatus::Failed->value,
'message' => 'Critical failure: ' . $e->getMessage()
]);
} }
return $this->resError("Critical failure occurred: " . $e->getMessage());
}
}
public function syncTaskDetailSubmit($uuid, $token) public function syncTaskDetailSubmit($uuid, $token)
@@ -334,5 +489,59 @@ class ServiceSIMBG
throw $e; 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;
}
}
} }

View File

@@ -15,7 +15,8 @@
"laravel/framework": "^11.31", "laravel/framework": "^11.31",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.0",
"laravel/tinker": "^2.9", "laravel/tinker": "^2.9",
"maatwebsite/excel": "^3.1" "maatwebsite/excel": "^3.1",
"openai-php/client": "^0.10.3"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",

300
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "52617d098d62b15c6ce8538cc8aea775", "content-hash": "41bb51871a746904ab745e4095db8b46",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@@ -3296,6 +3296,97 @@
], ],
"time": "2024-11-21T10:39:51+00:00" "time": "2024-11-21T10:39:51+00:00"
}, },
{
"name": "openai-php/client",
"version": "v0.10.3",
"source": {
"type": "git",
"url": "https://github.com/openai-php/client.git",
"reference": "4a565d145e0fb3ea1baba8fffe39d86c56b6dc2c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/openai-php/client/zipball/4a565d145e0fb3ea1baba8fffe39d86c56b6dc2c",
"reference": "4a565d145e0fb3ea1baba8fffe39d86c56b6dc2c",
"shasum": ""
},
"require": {
"php": "^8.1.0",
"php-http/discovery": "^1.20.0",
"php-http/multipart-stream-builder": "^1.4.2",
"psr/http-client": "^1.0.3",
"psr/http-client-implementation": "^1.0.1",
"psr/http-factory-implementation": "*",
"psr/http-message": "^1.1.0|^2.0.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^7.9.2",
"guzzlehttp/psr7": "^2.7.0",
"laravel/pint": "^1.18.1",
"mockery/mockery": "^1.6.12",
"nunomaduro/collision": "^7.11.0|^8.5.0",
"pestphp/pest": "^2.36.0|^3.5.0",
"pestphp/pest-plugin-arch": "^2.7|^3.0",
"pestphp/pest-plugin-type-coverage": "^2.8.7|^3.1.0",
"phpstan/phpstan": "^1.12.7",
"symfony/var-dumper": "^6.4.11|^7.1.5"
},
"type": "library",
"autoload": {
"files": [
"src/OpenAI.php"
],
"psr-4": {
"OpenAI\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nuno Maduro",
"email": "enunomaduro@gmail.com"
},
{
"name": "Sandro Gehri"
}
],
"description": "OpenAI PHP is a supercharged PHP API client that allows you to interact with the Open AI API",
"keywords": [
"GPT-3",
"api",
"client",
"codex",
"dall-e",
"language",
"natural",
"openai",
"php",
"processing",
"sdk"
],
"support": {
"issues": "https://github.com/openai-php/client/issues",
"source": "https://github.com/openai-php/client/tree/v0.10.3"
},
"funding": [
{
"url": "https://www.paypal.com/paypalme/enunomaduro",
"type": "custom"
},
{
"url": "https://github.com/gehrisandro",
"type": "github"
},
{
"url": "https://github.com/nunomaduro",
"type": "github"
}
],
"time": "2024-11-12T20:51:16+00:00"
},
{ {
"name": "paragonie/constant_time_encoding", "name": "paragonie/constant_time_encoding",
"version": "v3.0.0", "version": "v3.0.0",
@@ -3413,6 +3504,141 @@
}, },
"time": "2020-10-15T08:29:30+00:00" "time": "2020-10-15T08:29:30+00:00"
}, },
{
"name": "php-http/discovery",
"version": "1.20.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d",
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0|^2.0",
"php": "^7.1 || ^8.0"
},
"conflict": {
"nyholm/psr7": "<1.0",
"zendframework/zend-diactoros": "*"
},
"provide": {
"php-http/async-client-implementation": "*",
"php-http/client-implementation": "*",
"psr/http-client-implementation": "*",
"psr/http-factory-implementation": "*",
"psr/http-message-implementation": "*"
},
"require-dev": {
"composer/composer": "^1.0.2|^2.0",
"graham-campbell/phpspec-skip-example-extension": "^5.0",
"php-http/httplug": "^1.0 || ^2.0",
"php-http/message-factory": "^1.0",
"phpspec/phpspec": "^5.1 || ^6.1 || ^7.3",
"sebastian/comparator": "^3.0.5 || ^4.0.8",
"symfony/phpunit-bridge": "^6.4.4 || ^7.0.1"
},
"type": "composer-plugin",
"extra": {
"class": "Http\\Discovery\\Composer\\Plugin",
"plugin-optional": true
},
"autoload": {
"psr-4": {
"Http\\Discovery\\": "src/"
},
"exclude-from-classmap": [
"src/Composer/Plugin.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations",
"homepage": "http://php-http.org",
"keywords": [
"adapter",
"client",
"discovery",
"factory",
"http",
"message",
"psr17",
"psr7"
],
"support": {
"issues": "https://github.com/php-http/discovery/issues",
"source": "https://github.com/php-http/discovery/tree/1.20.0"
},
"time": "2024-10-02T11:20:13+00:00"
},
{
"name": "php-http/multipart-stream-builder",
"version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/php-http/multipart-stream-builder.git",
"reference": "10086e6de6f53489cca5ecc45b6f468604d3460e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/multipart-stream-builder/zipball/10086e6de6f53489cca5ecc45b6f468604d3460e",
"reference": "10086e6de6f53489cca5ecc45b6f468604d3460e",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
"php-http/discovery": "^1.15",
"psr/http-factory-implementation": "^1.0"
},
"require-dev": {
"nyholm/psr7": "^1.0",
"php-http/message": "^1.5",
"php-http/message-factory": "^1.0.2",
"phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Http\\Message\\MultipartStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com"
}
],
"description": "A builder class that help you create a multipart stream",
"homepage": "http://php-http.org",
"keywords": [
"factory",
"http",
"message",
"multipart stream",
"stream"
],
"support": {
"issues": "https://github.com/php-http/multipart-stream-builder/issues",
"source": "https://github.com/php-http/multipart-stream-builder/tree/1.4.2"
},
"time": "2024-09-04T13:22:54+00:00"
},
{ {
"name": "phpoffice/phpspreadsheet", "name": "phpoffice/phpspreadsheet",
"version": "1.29.10", "version": "1.29.10",
@@ -7155,74 +7381,6 @@
}, },
"time": "2020-07-09T08:09:16+00:00" "time": "2020-07-09T08:09:16+00:00"
}, },
{
"name": "ibex/crud-generator",
"version": "v2.1.2",
"source": {
"type": "git",
"url": "https://github.com/awais-vteams/laravel-crud-generator.git",
"reference": "3906f4a702c91bbe3a84d940c3021d1511834320"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/awais-vteams/laravel-crud-generator/zipball/3906f4a702c91bbe3a84d940c3021d1511834320",
"reference": "3906f4a702c91bbe3a84d940c3021d1511834320",
"shasum": ""
},
"require": {
"laravel/framework": "^10.30|^11.0",
"php": "^8.2"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Ibex\\CrudGenerator\\CrudServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Ibex\\CrudGenerator\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "M Awais",
"email": "asargodha@gmail.com"
}
],
"description": "Laravel CRUD Generator",
"keywords": [
"alpine js",
"bootstrap css",
"crud",
"crud generator",
"laravel",
"laravel crud generator",
"laravel package",
"tailwind css"
],
"support": {
"issues": "https://github.com/awais-vteams/laravel-crud-generator/issues",
"source": "https://github.com/awais-vteams/laravel-crud-generator/tree/v2.1.2"
},
"funding": [
{
"url": "https://github.com/awais-vteams",
"type": "github"
},
{
"url": "https://ko-fi.com/mawais",
"type": "ko_fi"
}
],
"time": "2024-12-09T06:01:54+00:00"
},
{ {
"name": "laravel/pail", "name": "laravel/pail",
"version": "v1.2.2", "version": "v1.2.2",
@@ -9316,12 +9474,12 @@
], ],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": [], "stability-flags": {},
"prefer-stable": true, "prefer-stable": true,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": "^8.2" "php": "^8.2"
}, },
"platform-dev": [], "platform-dev": {},
"plugin-api-version": "2.6.0" "plugin-api-version": "2.6.0"
} }

View File

@@ -123,5 +123,6 @@ return [
'store' => env('APP_MAINTENANCE_STORE', 'database'), '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
]; ];

View File

@@ -109,4 +109,10 @@ return [
'table' => 'failed_jobs', 'table' => 'failed_jobs',
], ],
// set timeout queue
'worker' => [
'timeout' => 300
]
]; ];

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('bigdata_resumes', function (Blueprint $table) {
$table->integer('spatial_count')->default(0);
$table->decimal('spatial_sum', 20,2)->default(0);
$table->string('year');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('bigdata_resumes', function (Blueprint $table) {
$table->dropColumn('spatial_count');
$table->dropColumn('spatial_sum');
$table->dropColumn('year');
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('pbg_task_google_sheet', function (Blueprint $table) {
$table->string('formatted_registration_number')->nullable()->after('no_registrasi');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('pbg_task_google_sheet', function (Blueprint $table) {
$table->dropColumn('formatted_registration_number');
});
}
};

View File

@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('bigdata_resumes', function (Blueprint $table) {
$table->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');
});
}
};

View File

@@ -71,7 +71,7 @@ class UsersRoleMenuSeeder extends Seeder
[ [
"name" => "Laporan", "name" => "Laporan",
"url" => "/laporan", "url" => "/laporan",
"icon" => "mingcute:task-line", "icon" => "mingcute:report-line",
"parent_id" => null, "parent_id" => null,
"sort_order" => 6, "sort_order" => 6,
] ]
@@ -214,6 +214,13 @@ class UsersRoleMenuSeeder extends Seeder
"parent_id" => $laporan->id, "parent_id" => $laporan->id,
"sort_order" => 1, "sort_order" => 1,
], ],
[
"name" => "Lap Pimpinan",
"url" => "bigdata-resumes",
"icon" => null,
"parent_id" => $laporan->id,
"sort_order" => 2,
],
]; ];
foreach ($children_menus as $child_menu) { foreach ($children_menus as $child_menu) {
@@ -237,6 +244,7 @@ class UsersRoleMenuSeeder extends Seeder
$spatial_plannings = Menu::where('name', 'Tata Ruang')->first(); $spatial_plannings = Menu::where('name', 'Tata Ruang')->first();
$pdam = Menu::where('name', 'PDAM')->first(); $pdam = Menu::where('name', 'PDAM')->first();
$peta = Menu::where('name', 'PETA')->first(); $peta = Menu::where('name', 'PETA')->first();
$bigdata_resume = Menu::where('name', 'Lap Pimpinan')->first();
// Superadmin gets all menus // Superadmin gets all menus
$superadmin->menus()->sync([ $superadmin->menus()->sync([
@@ -265,6 +273,7 @@ class UsersRoleMenuSeeder extends Seeder
$spatial_plannings->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $spatial_plannings->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$pdam->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $pdam->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$peta->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $peta->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$bigdata_resume->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
]); ]);
// Admin gets limited menus // Admin gets limited menus
@@ -279,6 +288,7 @@ class UsersRoleMenuSeeder extends Seeder
$dashboard->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $dashboard->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$data->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $data->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
]); ]);
// Attach User to role super admin // Attach User to role super admin
User::findOrFail(1)->roles()->sync([$superadmin->id]); User::findOrFail(1)->roles()->sync([$superadmin->id]);
} }

38
deploy.sh Normal file
View File

@@ -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!"

7
package-lock.json generated
View File

@@ -15,6 +15,7 @@
"gridjs": "^5.1.0", "gridjs": "^5.1.0",
"iconify-icon": "^2.1.0", "iconify-icon": "^2.1.0",
"jsvectormap": "^1.5.1", "jsvectormap": "^1.5.1",
"leaflet": "^1.9.4",
"moment": "^2.29.4", "moment": "^2.29.4",
"node-waves": "^0.7.6", "node-waves": "^0.7.6",
"quill": "^1.3.7", "quill": "^1.3.7",
@@ -1467,6 +1468,12 @@
"vite": "^5.0.0 || ^6.0.0" "vite": "^5.0.0 || ^6.0.0"
} }
}, },
"node_modules/leaflet": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause"
},
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",

View File

@@ -26,6 +26,7 @@
"gridjs": "^5.1.0", "gridjs": "^5.1.0",
"iconify-icon": "^2.1.0", "iconify-icon": "^2.1.0",
"jsvectormap": "^1.5.1", "jsvectormap": "^1.5.1",
"leaflet": "^1.9.4",
"moment": "^2.29.4", "moment": "^2.29.4",
"node-waves": "^0.7.6", "node-waves": "^0.7.6",
"quill": "^1.3.7", "quill": "^1.3.7",

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -5,6 +5,7 @@ use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true)); define('LARAVEL_START', microtime(true));
ini_set('max_execution_time',14400); ini_set('max_execution_time',14400);
ini_set('memory_limit', '2G');
// Determine if the application is in maintenance mode... // Determine if the application is in maintenance mode...
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) { if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {

View File

@@ -0,0 +1,11 @@
{
"RETRIBUTION": {
"prompt": "You are a MariaDB SQL expert. Your task is to generate an efficient and optimized SQL query based on user input.\n\n The query should retrieve data from the view table `v_pbg_task_with_retributions`, specifically selecting the following columns:\n\n - nilai_retribusi_bangunan\n - land_certificate_phase\n - due_date\n - consultation_type\n - function_type\n - slf_status_name\n - slf_status\n - status_name\n - status\n - address\n - document_number\n - registration_number\n - application_type_name\n - application_type\n - owner_name\n - name\n\n Ensure the query is well-structured, uses best indexing practices, and avoids performance bottlenecks.\n\n Consider the following context: \"$mainContent\".\n\n Always return only the SQL query without any additional explanation."
},
"DOCUMENT VALIDATION": {
"prompt": "You are a MariaDB SQL expert. Your task is to generate an efficient and optimized SQL query based on user input.\n\n The query should retrieve data from the view table `pbg_task`, specifically selecting the following columns:\n\n - name\n - owner_name\n - application_type\n - application_type_name\n - registration_number\n - document_number\n - address\n - status_name\n - slf_status\n - slf_status_name\n - function_type\n - consultation_type\n - function_type\n - consultation_type\n - due_date\n - land_certificate_phase\n\n Ensure the query is well-structured, uses best indexing practices, and avoids performance bottlenecks.\n\n Consider the following context: \"$mainContent\".\n\n Always return only the SQL query without any additional explanation."
},
"DATA SUMMARY": {
"prompt": "You are a MariaDB SQL expert. Your task is to generate an efficient and optimized SQL query based on user input.\n\n The query should retrieve data from the view table `bigdata_resumes`, specifically selecting the following columns:\n\n - potention_count\n - potention_sum\n - non_verified_count\n - non_verified_sum\n - verified_sum\n - verified_count\n - business_count\n - business_sum\n - non_business_count\n - non_business_sum\n - spatial_count\n - spatial_sum\n - updated_at\n\n Ensure the query is well-structured, uses best indexing practices, and avoids performance bottlenecks.\n\n Consider the following context: \"$mainContent\".\n\n Always return only the SQL query without any additional explanation."
}
}

View File

@@ -0,0 +1,170 @@
import { Grid } from "gridjs/dist/gridjs.umd.js";
import gridjs from "gridjs/dist/gridjs.umd.js";
import "gridjs/dist/gridjs.umd.js";
import GlobalConfig, { addThousandSeparators } from "../global-config.js";
import Swal from "sweetalert2";
import moment from "moment";
class BigdataResume {
constructor() {
this.toastMessage = document.getElementById("toast-message");
this.toastElement = document.getElementById("toastNotification");
this.toast = new bootstrap.Toast(this.toastElement);
this.table = null;
// Initialize functions
this.initTableDataSettings();
// this.initEvents();
}
initEvents() {
document.body.addEventListener("click", async (event) => {
const deleteButton = event.target.closest(
".btn-delete-data-settings"
);
if (deleteButton) {
event.preventDefault();
await this.handleDelete(deleteButton);
}
});
}
initTableDataSettings() {
let tableContainer = document.getElementById("table-bigdata-resumes");
this.table = new Grid({
columns: [
{ name: "ID" },
{ 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: "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: 50,
server: {
url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${
page + 1
}`,
},
},
sort: true,
search: {
server: {
url: (prev, keyword) => `${prev}?search=${keyword}`,
},
},
server: {
url: `${GlobalConfig.apiHost}/api/bigdata-report`,
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
},
then: (data) => {
return data.data.map((item) => [
item.id,
item.potention_count,
addThousandSeparators(item.potention_sum),
item.non_verified_count,
addThousandSeparators(item.non_verified_sum),
item.verified_count,
addThousandSeparators(item.verified_sum),
item.business_count,
addThousandSeparators(item.business_sum),
item.non_business_count,
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);
}
async handleDelete(deleteButton) {
const id = deleteButton.getAttribute("data-id");
const result = await Swal.fire({
title: "Are you sure?",
text: "You won't be able to revert this!",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
confirmButtonText: "Yes, delete it!",
});
if (result.isConfirmed) {
try {
let response = await fetch(
`${GlobalConfig.apiHost}/api/data-settings/${id}`,
{
method: "DELETE",
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
},
}
);
if (response.ok) {
let result = await response.json();
this.toastMessage.innerText =
result.message || "Deleted successfully!";
this.toast.show();
// Refresh Grid.js table
if (typeof this.table !== "undefined") {
this.table.updateConfig({}).forceRender();
}
} else {
let error = await response.json();
console.error("Delete failed:", error);
this.toastMessage.innerText =
error.message || "Delete failed!";
this.toast.show();
}
} catch (error) {
console.error("Error deleting item:", error);
this.toastMessage.innerText = "An error occurred!";
this.toast.show();
}
}
}
}
document.addEventListener("DOMContentLoaded", function (e) {
new BigdataResume();
});

View File

@@ -5,9 +5,9 @@ Dropzone.autoDiscover = false;
var previewTemplate, var previewTemplate,
dropzone, dropzone,
dropzonePreviewNode = document.querySelector("#dropzone-preview-list"); dropzonePreviewNode = document.querySelector("#dropzone-preview-list");
console.log(previewTemplate);
console.log(dropzone); const uploadButton = document.getElementById("btnUploadBusinessIndustry");
console.log(dropzonePreviewNode); const spinner = document.getElementById("spinner");
const toastNotification = document.getElementById("toastNotification"); const toastNotification = document.getElementById("toastNotification");
const toast = new bootstrap.Toast(toastNotification); const toast = new bootstrap.Toast(toastNotification);
@@ -29,97 +29,50 @@ const toast = new bootstrap.Toast(toastNotification);
.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
document.getElementById("toast-message").innerText = document.getElementById("toast-message").innerText =
response.message; response.message;
toast.show(); toast.show();
document.getElementById("submit-upload").innerHTML =
"Upload Files";
// Tunggu sebentar lalu reload halaman
setTimeout(() => { setTimeout(() => {
window.location.href = "/data/business-industries"; window.location.href = "/data/business-industries";
}, 2000); }, 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 uploading file:", file);
console.error("Error message:", errorMessage); console.error("Error message:", errorMessage);
// Handle the error response
// Show error toast
document.getElementById("toast-message").innerText = document.getElementById("toast-message").innerText =
errorMessage.message; errorMessage.message;
toast.show(); toast.show();
document.getElementById("submit-upload").innerHTML = uploadButton.disabled = false;
"Upload Files"; spinner.classList.add("d-none");
}); });
}, },
}))); })));
// Add event listener to control the submission manually // Add event listener to control the submission manually
document.querySelector("#submit-upload").addEventListener("click", function () { document
.querySelector("#btnUploadBusinessIndustry")
.addEventListener("click", function () {
console.log("Ini adalah value dropzone", dropzone.files[0]); console.log("Ini adalah value dropzone", dropzone.files[0]);
const formData = new FormData(); const formData = new FormData();
console.log("Dropzonefiles", dropzone.files);
this.innerHTML =
'<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>Loading...';
// Pastikan ada file dalam queue sebelum memprosesnya
if (dropzone.files.length > 0) { 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
uploadButton.disabled = true;
spinner.classList.remove("d-none");
} else { } else {
// Show error toast when no file is selected
document.getElementById("toast-message").innerText = document.getElementById("toast-message").innerText =
"Please add a file first."; "Please add a file first.";
toast.show(); toast.show();
uploadButton.disabled = false;
document.getElementById("submit-upload").innerHTML = "Upload Files"; spinner.classList.add("d-none");
} }
}); });
// Optional: Listen for the 'addedfile' event to log or control file add behavior dropzone.on("addedfile", function (file) {});
dropzone.on("addedfile", function (file) {
console.log("File ditambahkan:", file);
console.log("Nama File:", file.name);
console.log("Tipe File:", file.type);
console.log("Ukuran File:", (file.size / 1024).toFixed(2) + " KB");
});
dropzone.on("complete", function (file) { dropzone.on("complete", function (file) {
dropzone.removeFile(file); dropzone.removeFile(file);
}); });
// 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");
// // Remove existing icon (if any) before adding the new one
// const existingIcon = toastHeader.querySelector(".bx");
// if (existingIcon) {
// 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";
// icon.style.color = iconColor;
// toastHeader.querySelector(".auth-logo").appendChild(icon);
// // Set the toast message
// toastBody.textContent = message;
// // Show the toast
// const toast = new bootstrap.Toast(toastElement); // Inisialisasi Bootstrap Toast
// toast.show();
// }

View File

@@ -64,7 +64,7 @@ class BusinessIndustries {
}, },
], ],
pagination: { pagination: {
limit: 15, limit: 50,
server: { server: {
url: (prev, page) => url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${ `${prev}${prev.includes("?") ? "&" : "?"}page=${

View File

@@ -0,0 +1,176 @@
import GlobalConfig from "../global-config.js";
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".nav-link").forEach(tab => {
tab.addEventListener("click", function () {
setTimeout(() => {
const tab_active = getActiveTabId();
console.log("Active Tab ID:", tab_active);
}, 100); // Timeout untuk memastikan class `active` sudah diperbarui
});
});
const textarea = document.getElementById("user-message");
const sendButton = document.getElementById("send");
const conversationArea = document.querySelector(".row.flex-grow");
// Fungsi untuk mengirim pesan
async function sendMessage() {
const userText = textarea.value.trim();
if (userText !== "") {
// Kosongkan textarea setelah mengirim
textarea.value = "";
// Ambil tab aktif saat ini
const currentTab = getActiveTabId();
// Tambahkan pesan user ke UI
addMessage(userText, "user");
// Tambahkan pesan bot sementara dengan "Loading..."
const botMessageElement = addMessage('<div class="loader w-auto"></div>', "bot");
// Panggil API untuk mendapatkan response dari bot
const botResponse = await getBotResponse(currentTab, userText);
// Perbarui pesan bot dengan respons yang sebenarnya
botMessageElement.innerHTML = botResponse;;
}
}
// Event listener untuk klik tombol
sendButton.addEventListener("click", sendMessage);
// Event listener untuk menekan Enter di textarea
textarea.addEventListener("keydown", function (event) {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault(); // Mencegah newline di textarea
sendMessage(); // Panggil fungsi kirim pesan
}
});
function addMessage(text, sender) {
const messageRow = document.createElement("div");
messageRow.classList.add("row", "flex-grow", "overflow-auto");
const messageCol = document.createElement("div");
messageCol.classList.add("w-auto", "d-inline-block"); // Menyesuaikan lebar konten
if (sender === "user") {
messageCol.classList.add("ms-auto", "max-w-50"); // Rata kanan, max 50% (setara col-6)
} else {
messageCol.classList.add("max-w-75"); // Max 75% (setara col-9)
// Tambahkan avatar hanya untuk bot
const avatarSpan = document.createElement("span");
avatarSpan.classList.add("d-flex", "align-items-center", "mb-1");
const avatarImg = document.createElement("img");
avatarImg.classList.add("rounded-circle");
avatarImg.width = 32;
avatarImg.src = "/images/iconchatbot.jpeg";
avatarImg.alt = "bot-avatar";
avatarSpan.appendChild(avatarImg);
messageCol.appendChild(avatarSpan);
}
const messageDiv = document.createElement("div");
messageDiv.classList.add("p-2", "rounded", "mb-2");
if (sender === "user") {
messageDiv.classList.add("user-response", "bg-primary", "text-white");
} else {
messageDiv.classList.add("bot-response", "bg-light");
}
// Menyisipkan konten HTML langsung (bisa berupa teks atau loader)
messageDiv.innerHTML = text;
messageCol.appendChild(messageDiv);
messageRow.appendChild(messageCol);
// Tambahkan ke area percakapan
conversationArea.appendChild(messageRow);
// Scroll otomatis ke bawah
conversationArea.scrollTop = conversationArea.scrollHeight;
return messageDiv; // Mengembalikan elemen agar bisa diperbarui nanti
}
// function addMessage(text, sender) {
// const messageRow = document.createElement("div");
// messageRow.classList.add("row", "flex-grow", "overflow-auto");
// const messageCol = document.createElement("div");
// messageCol.classList.add("col-9", "w-auto");
// if (sender === "user") {
// messageCol.classList.add("ms-auto"); // Geser ke kanan untuk user
// }
// const messageDiv = document.createElement("div");
// messageDiv.classList.add("p-2", "rounded", "mb-2");
// if (sender === "user") {
// messageDiv.classList.add("user-response", "bg-primary", "text-white");
// } else {
// messageDiv.classList.add("bot-response", "bg-light");
// // Tambahkan avatar hanya untuk bot
// const avatarSpan = document.createElement("span");
// avatarSpan.classList.add("d-flex", "align-items-center", "mb-1");
// const avatarImg = document.createElement("img");
// avatarImg.classList.add("rounded-circle");
// avatarImg.width = 32;
// avatarImg.src = "/images/iconchatbot.jpeg";
// avatarImg.alt = "bot-avatar";
// avatarSpan.appendChild(avatarImg);
// messageCol.appendChild(avatarSpan);
// }
// // Menyisipkan konten HTML langsung (bisa berupa teks atau loader)
// messageDiv.innerHTML = text;
// messageCol.appendChild(messageDiv);
// messageRow.appendChild(messageCol);
// // Tambahkan ke area percakapan
// conversationArea.appendChild(messageRow);
// // Scroll otomatis ke bawah
// conversationArea.scrollTop = conversationArea.scrollHeight;
// return messageDiv; // Mengembalikan elemen agar bisa diperbarui nanti
// }
// Fungsi untuk memanggil API
async function getBotResponse(tab_active, userText) {
try {
const url = `${GlobalConfig.apiHost}/api/generate-text`;
const response = await fetch(url, {
method: "POST",
body: JSON.stringify({tab_active:tab_active, prompt: userText }),
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
},
});
const data = await response.json();
return data.response || "Maaf, saya tidak mengerti.";
} catch (error) {
console.error("Error fetching bot response:", error);
return "Terjadi kesalahan, coba lagi nanti.";
}
}
});
function getActiveTabId() {
const activeTab = document.querySelector(".nav-link.active");
return activeTab ? activeTab.id : null;
}

View File

@@ -53,7 +53,7 @@ class Customers {
}, },
], ],
pagination: { pagination: {
limit: 15, limit: 50,
server: { server: {
url: (prev, page) => url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${ `${prev}${prev.includes("?") ? "&" : "?"}page=${

View File

@@ -23,121 +23,9 @@ class BigData {
} }
async updateData(filterDate) { async updateData(filterDate) {
try { try {
console.log("Filtering data for date:", filterDate);
this.resumeBigData = await this.getBigDataResume(filterDate); this.resumeBigData = await this.getBigDataResume(filterDate);
// this.totalTargetPAD = await this.getDataSettings("TARGET_PAD");
// this.resultDataTotal = await this.getDataTotalPotensi(year);
// this.dataVerification = await this.getDataVerfication(year);
// this.dataNonVerification = await this.getDataNonVerfication(year);
// this.dataBusiness = await this.getDataBusiness(year);
// this.dataNonBusiness = await this.getDataNonBusiness(year);
// this.dataTataRuang = await this.getDataSettings("TATA_RUANG");
// this.dataSumRealisasiTerbit = await this.getDataSettings(
// "REALISASI_TERBIT_PBG_SUM"
// );
// this.dataCountRealisasiTerbit = await this.getDataSettings(
// "REALISASI_TERBIT_PBG_COUNT"
// );
// this.dataSumMenungguKlikDPMPTSP = await this.getDataSettings(
// "MENUNGGU_KLIK_DPMPTSP_SUM"
// );
// this.dataCountMenungguKlikDPMPTSP = await this.getDataSettings(
// "MENUNGGU_KLIK_DPMPTSP_COUNT"
// );
// this.dataSumProsesDinasTeknis = await this.getDataSettings(
// "PROSES_DINAS_TEKNIS_SUM"
// );
// this.dataCountProsesDinasTeknis = await this.getDataSettings(
// "PROSES_DINAS_TEKNIS_COUNT"
// );
// // total potensi this.initChartTargetPAD(filterDate);
// this.bigTargetPAD = new Big(this.totalTargetPAD ?? 0);
// this.bigTotalPotensi = new Big(this.resultDataTotal.totalData ?? 0);
// this.resultPercentage = 0;
// if (this.bigTotalPotensi > 0 && this.bigTargetPAD > 0) {
// this.resultPercentage = this.bigTotalPotensi
// .div(this.bigTargetPAD)
// .times(100)
// .toFixed(2);
// if (this.resultPercentage > 100) {
// this.resultPercentage = 100;
// }
// }
// // tata ruang
// this.bigTotalTataRuang = new Big(this.dataTataRuang);
// this.percentageResultTataRuang =
// this.bigTotalTataRuang <= 0 || this.bigTotalPotensi <= 0
// ? 0
// : this.bigTotalTataRuang
// .div(this.bigTotalPotensi)
// .times(100)
// .toFixed(2);
// // kekurangan potensi
// this.totalKekuranganPotensi = new Big(
// this.bigTargetPAD - this.bigTotalPotensi
// );
// this.percentageKekuranganPotensi =
// this.totalKekuranganPotensi <= 0 || this.bigTargetPAD <= 0
// ? 0
// : this.totalKekuranganPotensi
// .div(this.bigTargetPAD)
// .times(100)
// .toFixed(2);
// // non-verification documents
// this.bigTotalNonVerification = new Big(
// this.dataNonVerification.total
// );
// this.percentageResultNonVerification =
// this.bigTotalNonVerification <= 0 || this.bigTotalPotensi <= 0
// ? 0
// : this.bigTotalNonVerification
// .div(this.bigTotalPotensi)
// .times(100)
// .toFixed(2);
// // verification documents
// this.bigTotalVerification = new Big(this.dataVerification.total);
// this.percetageResultVerification =
// this.bigTotalVerification <= 0 || this.bigTotalPotensi <= 0
// ? 0
// : this.bigTotalVerification
// .div(this.bigTargetPAD)
// .times(100)
// .toFixed(2);
// // business documents
// this.bigTotalBusiness = new Big(this.dataBusiness.total);
// this.percentageResultBusiness =
// this.bigTotalNonVerification <= 0 || this.bigTotalBusiness <= 0
// ? 0
// : this.bigTotalBusiness
// .div(this.bigTotalNonVerification)
// .times(100)
// .toFixed(2);
// // non-business documents
// this.bigTotalNonBusiness = new Big(this.dataNonBusiness.total);
// this.percentageResultNonBusiness =
// this.bigTotalNonBusiness <= 0 ||
// this.bigTotalNonVerification <= 0
// ? 0
// : this.bigTotalNonBusiness
// .div(this.bigTotalNonVerification)
// .times(100)
// .toFixed(2);
// if (!this.bigTargetPAD) {
// console.error("Failed to load chart data");
// return;
// }
this.initChartTargetPAD();
this.initChartUsaha(); this.initChartUsaha();
this.initChartNonUsaha(); this.initChartNonUsaha();
this.initChartTotalPotensi(); this.initChartTotalPotensi();
@@ -180,190 +68,17 @@ class BigData {
return null; return null;
} }
} }
async getDataTotalPotensi(year) {
try {
const response = await fetch(
`${GlobalConfig.apiHost}/api/all-task-documents?year=${year}`,
{
credentials: "include",
headers: {
Authorization: `Bearer ${
document.querySelector("meta[name='api-token']")
.content
}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) { initChartTargetPAD(filterDate) {
console.error("Network response was not ok", response); const year =
} filterDate === "latest"
? new Date().getFullYear()
const data = await response.json(); : new Date(filterDate).getFullYear();
return { document
countData: data.data.count, .querySelectorAll(".document-title.chart-target-pad")
totalData: data.data.total, .forEach((element) => {
}; element.innerText = `Target PAD ${year}`;
} catch (error) { });
console.error("Error fetching chart data:", error);
return null;
}
}
async getDataVerfication(year) {
try {
const response = await fetch(
`${GlobalConfig.apiHost}/api/verification-documents?year=${year}`,
{
credentials: "include",
headers: {
Authorization: `Bearer ${
document.querySelector("meta[name='api-token']")
.content
}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
console.error("Network response was not ok", response);
}
const data = await response.json();
return {
count: data.data.count,
total: data.data.total,
};
} catch (error) {
console.error("Error fetching chart data:", error);
return 0;
}
}
async getDataNonVerfication(year) {
try {
const response = await fetch(
`${GlobalConfig.apiHost}/api/non-verification-documents?year=${year}`,
{
credentials: "include",
headers: {
Authorization: `Bearer ${
document.querySelector("meta[name='api-token']")
.content
}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
console.error("Network response was not ok", response);
}
const data = await response.json();
return {
count: data.data.count,
total: data.data.total,
};
} catch (error) {
console.error("Error fetching chart data:", error);
return 0;
}
}
async getDataBusiness(year) {
try {
const response = await fetch(
`${GlobalConfig.apiHost}/api/business-documents?year=${year}`,
{
credentials: "include",
headers: {
Authorization: `Bearer ${
document.querySelector("meta[name='api-token']")
.content
}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
console.error("Network response was not ok", response);
}
const data = await response.json();
return {
count: data.data.count,
total: data.data.total,
};
} catch (error) {
console.error("Error fetching chart data:", error);
return 0;
}
}
async getDataNonBusiness(year) {
try {
const response = await fetch(
`${GlobalConfig.apiHost}/api/non-business-documents?year=${year}`,
{
credentials: "include",
headers: {
Authorization: `Bearer ${
document.querySelector("meta[name='api-token']")
.content
}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
console.error("Network response was not ok", response);
}
const data = await response.json();
return {
count: data.data.count,
total: data.data.total,
};
} catch (error) {
console.error("Error fetching chart data:", error);
return 0;
}
}
async getDataSettings(string_key) {
try {
const response = await fetch(
`${GlobalConfig.apiHost}/api/api-data-settings?search=${string_key}`,
{
credentials: "include",
headers: {
Authorization: `Bearer ${
document.querySelector("meta[name='api-token']")
.content
}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
console.error("Network response was not ok", response);
}
const data = await response.json();
return data.data[0].value;
} catch (error) {
console.error("Error fetching chart data:", error);
return 0;
}
}
initChartTargetPAD() {
document document
.querySelectorAll(".document-count.chart-target-pad") .querySelectorAll(".document-count.chart-target-pad")
.forEach((element) => { .forEach((element) => {
@@ -373,7 +88,6 @@ class BigData {
.querySelectorAll(".document-total.chart-target-pad") .querySelectorAll(".document-total.chart-target-pad")
.forEach((element) => { .forEach((element) => {
element.innerText = `Rp.${addThousandSeparators( element.innerText = `Rp.${addThousandSeparators(
// this.bigTargetPAD.toString()
this.resumeBigData.target_pad.sum.toString() this.resumeBigData.target_pad.sum.toString()
)}`; )}`;
}); });
@@ -589,20 +303,18 @@ class BigData {
document document
.querySelectorAll(".document-count.chart-potensi-tata-ruang") .querySelectorAll(".document-count.chart-potensi-tata-ruang")
.forEach((element) => { .forEach((element) => {
element.innerText = ""; element.innerText = `${this.resumeBigData.tata_ruang.count}`;
}); });
document document
.querySelectorAll(".document-total.chart-potensi-tata-ruang") .querySelectorAll(".document-total.chart-potensi-tata-ruang")
.forEach((element) => { .forEach((element) => {
element.innerText = `Rp.${addThousandSeparators( element.innerText = `Rp.${addThousandSeparators(
// this.bigTotalTataRuang.toString()
this.resumeBigData.tata_ruang.sum.toString() this.resumeBigData.tata_ruang.sum.toString()
)}`; )}`;
}); });
document document
.querySelectorAll(".small-percentage.chart-potensi-tata-ruang") .querySelectorAll(".small-percentage.chart-potensi-tata-ruang")
.forEach((element) => { .forEach((element) => {
// element.innerText = `${this.percentageResultTataRuang}%`;
element.innerText = `${this.resumeBigData.tata_ruang.percentage}%`; element.innerText = `${this.resumeBigData.tata_ruang.percentage}%`;
}); });
} }

View File

@@ -16,6 +16,19 @@ class LackOfPotential {
this.pdamCount = this.allCountData.total_pdam ?? 0; this.pdamCount = this.allCountData.total_pdam ?? 0;
this.tataRuangCount = this.allCountData.total_tata_ruang ?? 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.bigTargetPAD = new Big(this.totalTargetPAD ?? 0);
this.bigTotalPotensi = new Big(this.totalPotensi.total ?? 0); this.bigTotalPotensi = new Big(this.totalPotensi.total ?? 0);
this.bigTotalLackPotential = this.bigTargetPAD.minus( this.bigTotalLackPotential = this.bigTargetPAD.minus(
@@ -140,6 +153,15 @@ class LackOfPotential {
document.getElementById("pdam-count").innerText = this.pdamCount; document.getElementById("pdam-count").innerText = this.pdamCount;
document.getElementById("pbb-bangunan-count").innerText = document.getElementById("pbb-bangunan-count").innerText =
this.tataRuangCount; 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) { document.addEventListener("DOMContentLoaded", async function (e) {

View File

@@ -17,35 +17,64 @@ document.addEventListener("DOMContentLoaded", async function () {
targetPadElement.textContent = formatCurrency(targetPadValue); targetPadElement.textContent = formatCurrency(targetPadValue);
// Total Potensi Berkas // Total Potensi Berkas
const totalPotensiBerkas = document.getElementById("total-potensi-berkas"); const totalPotensiBerkas = document.getElementById(
"total-potensi-berkas"
);
if (!totalPotensiBerkas) return; if (!totalPotensiBerkas) return;
const totalPotensiBerkasValue = await getDataTotalPotensi(selectedYear); const totalPotensiBerkasValue = await getDataTotalPotensi(selectedYear);
totalPotensiBerkas.textContent = formatCurrency(totalPotensiBerkasValue.totalData); totalPotensiBerkas.textContent = formatCurrency(
totalPotensiBerkasValue.totalData
);
// Total Berkas Terverifikasi // Total Berkas Terverifikasi
const totalBerkasTerverifikasi = document.getElementById("total-berkas-terverifikasi"); const totalBerkasTerverifikasi = document.getElementById(
"total-berkas-terverifikasi"
);
if (!totalBerkasTerverifikasi) return; if (!totalBerkasTerverifikasi) return;
const totalBerkasTerverifikasiValue = await getDataVerification(selectedYear); const totalBerkasTerverifikasiValue = await getDataVerification(
totalBerkasTerverifikasi.textContent = formatCurrency(totalBerkasTerverifikasiValue.totalData); selectedYear
);
totalBerkasTerverifikasi.textContent = formatCurrency(
totalBerkasTerverifikasiValue.totalData
);
// Total Kekurangan potensi // Total Kekurangan potensi
const totalKekuranganPotensi = document.getElementById("total-kekurangan-potensi"); const totalKekuranganPotensi = document.getElementById(
"total-kekurangan-potensi"
);
if (!totalKekuranganPotensi) return; if (!totalKekuranganPotensi) return;
const totalKekuranganPotensiValue = new Big(targetPadValue) - new Big(totalPotensiBerkasValue.totalData); const totalKekuranganPotensiValue =
totalKekuranganPotensi.textContent = formatCurrency(totalKekuranganPotensiValue) new Big(targetPadValue) -
new Big(totalPotensiBerkasValue.totalData);
totalKekuranganPotensi.textContent = formatCurrency(
totalKekuranganPotensiValue
);
// Total Potensi PBG dari tata ruang // Total Potensi PBG dari tata ruang
const totalPotensiPBGTataRuang = document.getElementById("total-potensi-pbd-tata-ruang"); const totalPotensiPBGTataRuang = document.getElementById(
"total-potensi-pbd-tata-ruang"
);
if (!totalPotensiPBGTataRuang) return; if (!totalPotensiPBGTataRuang) return;
const totalPotensiPBGTataRuangValue = await getDataSettings("TATA_RUANG"); const totalPotensiPBGTataRuangValue = await getDataSettings(
totalPotensiPBGTataRuang.textContent = formatCurrency(totalPotensiPBGTataRuangValue); "TATA_RUANG"
);
totalPotensiPBGTataRuang.textContent = formatCurrency(
totalPotensiPBGTataRuangValue
);
// Total Berkas Belum terverifikasi // Total Berkas Belum terverifikasi
const totalBerkasBelumTerverifikasi = document.getElementById("total-berkas-belum-terverifikasi"); const totalBerkasBelumTerverifikasi = document.getElementById(
"total-berkas-belum-terverifikasi"
);
if (!totalBerkasBelumTerverifikasi) return; if (!totalBerkasBelumTerverifikasi) return;
const totalBerkasBelumTerverifikasiValue = await getDataNonVerification(selectedYear); const totalBerkasBelumTerverifikasiValue = await getDataNonVerification(
const totalBerkasBelumTerverifikasiCount = totalBerkasBelumTerverifikasiValue.countData; selectedYear
totalBerkasBelumTerverifikasi.textContent = formatCurrency(totalBerkasBelumTerverifikasiValue.totalData); );
const totalBerkasBelumTerverifikasiCount =
totalBerkasBelumTerverifikasiValue.countData;
totalBerkasBelumTerverifikasi.textContent = formatCurrency(
totalBerkasBelumTerverifikasiValue.totalData
);
// Total Berkas Usaha // Total Berkas Usaha
const totalBerkasUsahaValue = await getDataBusiness(selectedYear); const totalBerkasUsahaValue = await getDataBusiness(selectedYear);
@@ -58,21 +87,40 @@ document.addEventListener("DOMContentLoaded", async function () {
const totalBerkasNonUsahaTotalData = totalBerkasNonUsahaValue.totalData; const totalBerkasNonUsahaTotalData = totalBerkasNonUsahaValue.totalData;
// Pie Chart Section // Pie Chart Section
let persenUsaha = totalBerkasBelumTerverifikasiCount > 0 let persenUsaha =
? ((totalBerkasUsahaCount / totalBerkasBelumTerverifikasiCount) * 100).toFixed(2) totalBerkasBelumTerverifikasiCount > 0
? (
(totalBerkasUsahaCount /
totalBerkasBelumTerverifikasiCount) *
100
).toFixed(2)
: "0"; : "0";
let persenNonUsaha = totalBerkasBelumTerverifikasiCount > 0 let persenNonUsaha =
? ((totalBerkasNonUsahaCount / totalBerkasBelumTerverifikasiCount) * 100).toFixed(2) totalBerkasBelumTerverifikasiCount > 0
? (
(totalBerkasNonUsahaCount /
totalBerkasBelumTerverifikasiCount) *
100
).toFixed(2)
: "0"; : "0";
const dataSeriesPieChart = [Number(persenUsaha), Number(persenNonUsaha)] const dataSeriesPieChart = [
Number(persenUsaha),
Number(persenNonUsaha),
];
const labelsPieChart = ["Berkas Usaha", "Berkas Non Usaha"]; const labelsPieChart = ["Berkas Usaha", "Berkas Non Usaha"];
document.querySelector("td[data-category='non-usaha']").textContent = formatCurrency(totalBerkasNonUsahaTotalData).toLocaleString(); document.querySelector("td[data-category='non-usaha']").textContent =
document.querySelector("td[data-category='non-usaha-percentage']").textContent = persenNonUsaha + "%"; formatCurrency(totalBerkasNonUsahaTotalData).toLocaleString();
document.querySelector(
"td[data-category='non-usaha-percentage']"
).textContent = persenNonUsaha + "%";
document.querySelector("td[data-category='usaha']").textContent = formatCurrency(totalBerkasUsahaTotalData).toLocaleString(); document.querySelector("td[data-category='usaha']").textContent =
document.querySelector("td[data-category='usaha-percentage']").textContent = persenUsaha + "%"; formatCurrency(totalBerkasUsahaTotalData).toLocaleString();
document.querySelector(
"td[data-category='usaha-percentage']"
).textContent = persenUsaha + "%";
updatePieChart(dataSeriesPieChart, labelsPieChart); updatePieChart(dataSeriesPieChart, labelsPieChart);
@@ -81,27 +129,30 @@ document.addEventListener("DOMContentLoaded", async function () {
console.log(allLocation); console.log(allLocation);
// Filter hanya data yang memiliki angka valid // Filter hanya data yang memiliki angka valid
let validLocations = allLocation.dataLocation.filter(loc => { let validLocations = allLocation.dataLocation.filter((loc) => {
return !isNaN(parseFloat(loc.longitude)) && !isNaN(parseFloat(loc.latitude)); return (
!isNaN(parseFloat(loc.longitude)) &&
!isNaN(parseFloat(loc.latitude))
);
}); });
// Ubah string ke float // Ubah string ke float
validLocations = validLocations.map(loc => ({ validLocations = validLocations.map((loc) => ({
name: loc.project_name, name: loc.project_name,
longitude: parseFloat(loc.longitude), longitude: parseFloat(loc.longitude),
latitude: parseFloat(loc.latitude) latitude: parseFloat(loc.latitude),
})); }));
console.log(validLocations.name) console.log(validLocations.name);
// Inisialisasi peta // Inisialisasi peta
var map = L.map('map').setView([-7.0230, 107.5275], 10); var map = L.map("map").setView([-7.023, 107.5275], 10);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: '&copy; OpenStreetMap contributors' attribution: "&copy; OpenStreetMap contributors",
}).addTo(map); }).addTo(map);
// Tambahkan marker ke peta // Tambahkan marker ke peta
validLocations.forEach(function(loc) { validLocations.forEach(function (loc) {
L.marker([loc.latitude, loc.longitude]) L.marker([loc.latitude, loc.longitude])
.addTo(map) .addTo(map)
.bindPopup(`<b>${loc.name}</b>`) // Popup saat diklik .bindPopup(`<b>${loc.name}</b>`) // Popup saat diklik
@@ -109,22 +160,40 @@ document.addEventListener("DOMContentLoaded", async function () {
}); });
// Realisasi terbit PBG // Realisasi terbit PBG
const totalRealisasiTerbitPBG = document.getElementById("realisasi-terbit-pbg"); const totalRealisasiTerbitPBG = document.getElementById(
"realisasi-terbit-pbg"
);
if (!totalRealisasiTerbitPBG) return; if (!totalRealisasiTerbitPBG) return;
const totalRealisasiTerbitPBGValue = await getDataSettings("REALISASI_TERBIT_PBG_SUM"); const totalRealisasiTerbitPBGValue = await getDataSettings(
totalRealisasiTerbitPBG.textContent = formatCurrency(totalRealisasiTerbitPBGValue); "REALISASI_TERBIT_PBG_SUM"
);
totalRealisasiTerbitPBG.textContent = formatCurrency(
totalRealisasiTerbitPBGValue
);
// Menunggu Klik DPMPTSP // Menunggu Klik DPMPTSP
const totalMenungguKlikDpmptsp = document.getElementById("waiting-click-dpmptsp"); const totalMenungguKlikDpmptsp = document.getElementById(
"waiting-click-dpmptsp"
);
if (!totalMenungguKlikDpmptsp) return; if (!totalMenungguKlikDpmptsp) return;
const totalMenungguKlikDpmptspValue = await getDataSettings("MENUNGGU_KLIK_DPMPTSP_SUM"); const totalMenungguKlikDpmptspValue = await getDataSettings(
totalMenungguKlikDpmptsp.textContent = formatCurrency(totalMenungguKlikDpmptspValue); "MENUNGGU_KLIK_DPMPTSP_SUM"
);
totalMenungguKlikDpmptsp.textContent = formatCurrency(
totalMenungguKlikDpmptspValue
);
// Proses Dinas Teknis // Proses Dinas Teknis
const totalProsesDinasTeknis = document.getElementById("processing-technical-services"); const totalProsesDinasTeknis = document.getElementById(
"processing-technical-services"
);
if (!totalProsesDinasTeknis) return; if (!totalProsesDinasTeknis) return;
const totalProsesDinasTeknisValue = await getDataSettings("PROSES_DINAS_TEKNIS_SUM"); const totalProsesDinasTeknisValue = await getDataSettings(
totalProsesDinasTeknis.textContent = formatCurrency(totalProsesDinasTeknisValue); "PROSES_DINAS_TEKNIS_SUM"
);
totalProsesDinasTeknis.textContent = formatCurrency(
totalProsesDinasTeknisValue
);
// Load Tabel Baru di Update // Load Tabel Baru di Update
const tableLastUpdated = new GeneralTable( const tableLastUpdated = new GeneralTable(
@@ -172,25 +241,32 @@ document.addEventListener("DOMContentLoaded", async function () {
tableSKPBGTerbit.init(); tableSKPBGTerbit.init();
document.querySelector("#pbg-filter-by-updated-at .gridjs-search").hidden = true; document.querySelector(
document.querySelector("#pbg-filter-by-updated-at .gridjs-footer").hidden = true; "#pbg-filter-by-updated-at .gridjs-search"
document.querySelector("#pbg-filter-by-status .gridjs-search").hidden = true; ).hidden = true;
document.querySelector("#pbg-filter-by-status .gridjs-footer").hidden = true; document.querySelector(
"#pbg-filter-by-updated-at .gridjs-footer"
).hidden = true;
document.querySelector(
"#pbg-filter-by-status .gridjs-search"
).hidden = true;
document.querySelector(
"#pbg-filter-by-status .gridjs-footer"
).hidden = true;
} }
await updateDataByYear(yearPicker.value); await updateDataByYear(yearPicker.value);
yearPicker.addEventListener("change", async function () { yearPicker.addEventListener("change", async function () {
console.log("event change dropdown") console.log("event change dropdown");
await updateDataByYear(yearPicker.value); await updateDataByYear(yearPicker.value);
}); });
}); });
async function getDataSettings(string_key) { async function getDataSettings(string_key) {
try { try {
const response = await fetch( const response = await fetch(
`${GlobalConfig.apiHost}/api/api-data-settings?search=${string_key}`, `${GlobalConfig.apiHost}/api/data-settings?search=${string_key}`,
{ {
credentials: "include", credentials: "include",
headers: { headers: {
@@ -224,7 +300,7 @@ async function getDataTotalPotensi(year) {
Authorization: `Bearer ${ Authorization: `Bearer ${
document.querySelector("meta[name='api-token']").content document.querySelector("meta[name='api-token']").content
}`, }`,
"Content-Type": "application/json" "Content-Type": "application/json",
}, },
} }
); );
@@ -251,8 +327,7 @@ async function getDataVerification(year) {
credentials: "include", credentials: "include",
headers: { headers: {
Authorization: `Bearer ${ Authorization: `Bearer ${
document.querySelector("meta[name='api-token']") document.querySelector("meta[name='api-token']").content
.content
}`, }`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -263,26 +338,25 @@ async function getDataVerification(year) {
console.error("Network response was not ok", response); console.error("Network response was not ok", response);
} }
const data = await response.json() const data = await response.json();
return { return {
totalData: data.data.total, totalData: data.data.total,
}; };
} catch (error) { } catch (error) {
console.error("Error fetching chart data:", error); console.error("Error fetching chart data:", error);
return 0 return 0;
} }
} }
async function getDataNonVerification(year) { async function getDataNonVerification(year) {
try { try {
const response = await fetch ( const response = await fetch(
`${GlobalConfig.apiHost}/api/non-verification-documents?year=${year}`, `${GlobalConfig.apiHost}/api/non-verification-documents?year=${year}`,
{ {
credentials: "include", credentials: "include",
headers: { headers: {
Authorization: `Bearer ${ Authorization: `Bearer ${
document.querySelector("meta[name='api-token']") document.querySelector("meta[name='api-token']").content
.content
}`, }`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -297,7 +371,7 @@ async function getDataNonVerification(year) {
return { return {
countData: data.data.count, countData: data.data.count,
totalData: data.data.total, totalData: data.data.total,
} };
} catch (error) { } catch (error) {
console.error("Error fetching chart data:", error); console.error("Error fetching chart data:", error);
return 0; return 0;
@@ -312,8 +386,7 @@ async function getDataBusiness(year) {
credentials: "include", credentials: "include",
headers: { headers: {
Authorization: `Bearer ${ Authorization: `Bearer ${
document.querySelector("meta[name='api-token']") document.querySelector("meta[name='api-token']").content
.content
}`, }`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -343,8 +416,7 @@ async function getDataNonBusiness(year) {
credentials: "include", credentials: "include",
headers: { headers: {
Authorization: `Bearer ${ Authorization: `Bearer ${
document.querySelector("meta[name='api-token']") document.querySelector("meta[name='api-token']").content
.content
}`, }`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -357,8 +429,8 @@ async function getDataNonBusiness(year) {
const data = await response.json(); const data = await response.json();
return { return {
countData: data.data.count, countData: data.data.count,
totalData: data.data.total totalData: data.data.total,
} };
} catch (error) { } catch (error) {
console.error("Error fetching chart data:", error); console.error("Error fetching chart data:", error);
return 0; return 0;
@@ -373,8 +445,7 @@ async function getAllLocation() {
credentials: "include", credentials: "include",
headers: { headers: {
Authorization: `Bearer ${ Authorization: `Bearer ${
document.querySelector("meta[name='api-token']") document.querySelector("meta[name='api-token']").content
.content
}`, }`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
@@ -386,7 +457,7 @@ async function getAllLocation() {
const data = await response.json(); const data = await response.json();
return { return {
dataLocation: data.data, dataLocation: data.data,
} };
} catch (error) { } catch (error) {
console.error("Error fetching chart data:", error); console.error("Error fetching chart data:", error);
return 0; return 0;
@@ -397,38 +468,40 @@ async function initChart() {
var options = { var options = {
chart: { chart: {
height: 180, height: 180,
type: 'donut', type: "donut",
}, },
series: [0, 0], // Inisialisasi dengan nilai awal series: [0, 0], // Inisialisasi dengan nilai awal
labels: ["Berkas Usaha", "Berkas Non Usaha"], labels: ["Berkas Usaha", "Berkas Non Usaha"],
legend: { legend: {
show: false show: false,
}, },
stroke: { stroke: {
width: 0 width: 0,
}, },
plotOptions: { plotOptions: {
pie: { pie: {
donut: { donut: {
size: '60%', size: "60%",
} },
} },
}, },
colors: ["#7e67fe", "#17c553"], colors: ["#7e67fe", "#17c553"],
dataLabels: { dataLabels: {
enabled: false enabled: false,
}, },
responsive: [{ responsive: [
{
breakpoint: 480, breakpoint: 480,
options: { options: {
chart: { chart: {
width: 200 width: 200,
} },
} },
}], },
],
fill: { fill: {
type: 'gradient' type: "gradient",
} },
}; };
chart = new ApexCharts(document.querySelector("#conversions"), options); chart = new ApexCharts(document.querySelector("#conversions"), options);
@@ -447,7 +520,7 @@ async function updatePieChart(dataSeries, labels) {
// Perbarui label jika diperlukan // Perbarui label jika diperlukan
if (Array.isArray(labels) && labels.length === dataSeries.length) { if (Array.isArray(labels) && labels.length === dataSeries.length) {
chart.updateOptions({ chart.updateOptions({
labels: labels labels: labels,
}); });
} }
} }
@@ -457,7 +530,7 @@ function formatCurrency(number) {
return new Intl.NumberFormat("id-ID", { return new Intl.NumberFormat("id-ID", {
style: "currency", style: "currency",
currency: "IDR", currency: "IDR",
minimumFractionDigits: 0 minimumFractionDigits: 0,
}).format(number); }).format(number);
} }
@@ -467,4 +540,4 @@ const pbgTaskColumns = [
"Nomor Registrasi", "Nomor Registrasi",
"Nomor Dokumen", "Nomor Dokumen",
"Alamat", "Alamat",
] ];

View File

@@ -20,9 +20,9 @@ const dataTourismsColumns = [
name: "Actions", name: "Actions",
widht: "120px", widht: "120px",
formatter: function (cell, row) { formatter: function (cell, row) {
const id = row.cells[10].data; const id = row.cells[11].data;
const long = row.cells[8].data; const long = row.cells[9].data;
const lat = row.cells[9].data; const lat = row.cells[10].data;
const model = "data/tourisms"; const model = "data/tourisms";
return gridjs.html(` return gridjs.html(`
<div class="d-flex justify-items-end gap-10"> <div class="d-flex justify-items-end gap-10">

View File

@@ -27,7 +27,7 @@ const dataUMKMColumns = [
name: "Actions", name: "Actions",
widht: "120px", widht: "120px",
formatter: function(cell, row) { formatter: function(cell, row) {
const id = row.cells[18].data; const id = row.cells[19].data;
const model = "data/umkm"; const model = "data/umkm";
return gridjs.html(` return gridjs.html(`
<div class="d-flex justify-items-end gap-10"> <div class="d-flex justify-items-end gap-10">

View File

@@ -0,0 +1,79 @@
import L from "leaflet";
import "leaflet/dist/leaflet.css";
document.addEventListener("DOMContentLoaded", function () {
var map = L.map("map").setView([-6.9175, 107.6191], 10); // Bandung
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "&copy; OpenStreetMap contributors",
}).addTo(map);
// Dapatkan elemen loading
const loadingDiv = document.getElementById("loading");
loadingDiv.style.display = "flex"; // Tampilkan loading
fetch("/storage/maps/rencana-polaruang.geojson")
.then((res) => res.json())
.then((geojson) => {
let colorMapping = {
BJ: "rgb(235, 30, 30)",
BA: "rgb(151, 219, 242)",
CA: "rgb(70, 70, 165)",
"P-2": "rgb(230, 255, 75)",
HL: "rgb(50, 95, 40)",
HPT: "rgb(75, 155, 55)",
HP: "rgb(125, 180, 55)",
W: "rgb(255, 165, 255)",
PTL: "rgb(0, 255, 205)",
"IK-2": "rgb(130, 185, 210)",
"P-3": "rgb(175, 175, 55)",
PS: "rgb(5, 215, 215)",
PD: "rgb(235, 155, 60)",
PK: "rgb(245, 155, 30)",
HK: "rgb(155, 0, 255)",
KPI: "rgb(105, 0, 0)",
MBT: "rgb(95, 115, 145)",
"P-4": "rgb(185, 235, 185)",
TB: "rgb(70, 150, 255)",
"P-1": "rgb(200, 245, 70)",
TR: "rgb(215, 55, 0)",
THR: "rgb(185, 165, 255)",
TWA: "rgb(210, 190, 255)",
};
var geoLayer = L.geoJSON(geojson, {
style: function (feature) {
let htmlString = feature.properties.description.toString();
let match = htmlString.match(
/<td>Kode Kawasan<\/td>\s*<td>(.*?)<\/td>/
);
console.log("Kode Kawasan ", match[1]);
let color_code = match[1];
return {
color: colorMapping[color_code],
fillColor: colorMapping[color_code] || "#cccccc",
fillOpacity: 0.6,
weight: 1.5,
};
},
onEachFeature: function (feature, layer) {
if (feature.properties && feature.properties.name) {
layer.bindPopup(feature.properties.name);
}
},
}).addTo(map);
map.fitBounds(geoLayer.getBounds());
// Sembunyikan loading setelah selesai render
loadingDiv.style.display = "none";
})
.catch((error) => {
console.error("Error loading GeoJSON:", error);
loadingDiv.innerHTML =
"<div class='loading-text' style='background: red;'>Failed to load data!</div>";
});
});

View File

@@ -31,7 +31,7 @@ class UsersTable {
}, },
], ],
pagination: { pagination: {
limit: 15, limit: 50,
server: { server: {
url: (prev, page) => url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${ `${prev}${prev.includes("?") ? "&" : "?"}page=${

View File

@@ -83,7 +83,7 @@ class Menus {
}, },
], ],
pagination: { pagination: {
limit: 15, limit: 50,
server: { server: {
url: (prev, page) => url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${ `${prev}${prev.includes("?") ? "&" : "?"}page=${

View File

@@ -52,7 +52,7 @@ class Roles {
}, },
], ],
pagination: { pagination: {
limit: 15, limit: 50,
server: { server: {
url: (prev, page) => url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${ `${prev}${prev.includes("?") ? "&" : "?"}page=${

View File

@@ -4,13 +4,21 @@ import "gridjs/dist/gridjs.umd.js";
import GlobalConfig from "../../global-config.js"; import GlobalConfig from "../../global-config.js";
class SyncronizeTask { 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() { init() {
this.initTableImportDatasources(); this.initTableImportDatasources();
this.handleSubmitSync(); this.handleSubmitSync();
this.handleSubmitSnycGoogleSheet();
} }
initTableImportDatasources() { initTableImportDatasources() {
new Grid({ let tableContainer = document.getElementById(
"table-import-datasources"
);
this.table = new gridjs.Grid({
columns: ["ID", "Message", "Response", "Status", "Created"], columns: ["ID", "Message", "Response", "Status", "Created"],
search: { search: {
server: { server: {
@@ -18,7 +26,7 @@ class SyncronizeTask {
}, },
}, },
pagination: { pagination: {
limit: 15, limit: 50,
server: { server: {
url: (prev, page) => url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${ `${prev}${prev.includes("?") ? "&" : "?"}page=${
@@ -45,20 +53,24 @@ class SyncronizeTask {
]), ]),
total: (data) => data.meta.total, total: (data) => data.meta.total,
}, },
}).render(document.getElementById("table-import-datasources")); }).render(tableContainer);
} }
handleSubmitSync() { handleSubmitSync() {
const button = document.getElementById("btn-sync-submit"); 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( fetch(
`${GlobalConfig.apiHost}/api/import-datasource/check-datasource`, `${GlobalConfig.apiHost}/api/import-datasource/check-datasource`,
{ {
method: "GET", method: "GET",
headers: { headers: {
Authorization: `Bearer ${document Authorization: `Bearer ${apiToken}`,
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
} }
@@ -70,91 +82,74 @@ class SyncronizeTask {
return response.json(); return response.json();
}) })
.then((data) => { .then((data) => {
console.log("data check button sync", data.can_execute);
button.disabled = !data.can_execute; button.disabled = !data.can_execute;
// If the button is enabled, add click event to trigger sync if (!data.can_execute) {
if (!button.disabled) { // Keep spinner visible if cannot execute
button.addEventListener("click", function (e) { spinner.classList.remove("d-none");
button.disabled = true; // Disable button to prevent multiple clicks } else {
button.textContent = "Syncing..."; // Change button text to show syncing // Hide spinner when execution is allowed
spinner.classList.add("d-none");
// Trigger the scraping API call // Remove previous event listener before adding a new one
fetch(`${GlobalConfig.apiHost}/api/scraping`, { button.removeEventListener("click", this.handleSyncClick);
method: "GET", button.addEventListener(
headers: { "click",
Authorization: `Bearer ${document this.handleSyncClick.bind(this)
.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
});
});
}
}) })
.catch((err) => { .catch((err) => {
console.error("Fetch error:", err); console.error("Fetch error:", err);
alert("An error occurred while checking the datasource"); 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 handleSyncClick() {
fetch(`${GlobalConfig.apiHost}/api/sync-pbg-task-google-sheet`, { 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", method: "GET",
headers: { headers: {
Authorization: `Bearer ${document Authorization: `Bearer ${apiToken}`,
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}) })
.then((response) => { .then(async (response) => {
if (!response.ok) { if (!response.ok) {
throw new Error("Network response was not ok"); throw new Error(`HTTP error! Status: ${response.status}`);
} }
return response.json();
let data;
try {
data = await response.json();
} catch (jsonError) {
throw new Error("Failed to parse JSON response");
}
return data;
}) })
.then((data) => { .then((data) => {
console.log("data sync button", data); this.toastMessage.innerText =
alert("Synchronization executed successfully"); data.data.message || "Synchronize successfully!";
window.location.reload(); this.toast.show();
// Update the table if it exists
if (this.table) {
this.table.updateConfig({}).forceRender();
}
}) })
.catch((err) => { .catch((err) => {
console.error("Fetch error:", err); console.error("Fetch error:", err);
alert("An error occurred during synchronization"); alert("An error occurred during synchronization" + err.message);
}) button.disabled = false;
.finally(() => {
button.disabled = false; // Re-enable the button after the request is complete
button.textContent = "Sync Google Sheet"; // Reset button text
});
}); });
} }
} }

View File

@@ -21,8 +21,8 @@
// overflow: hidden; // overflow: hidden;
.circle-content { .circle-content {
width: 180px; /* Ukuran lingkaran dalam */ min-width: 180px; /* Ukuran lingkaran dalam */
height: 180px; min-height: 180px;
background-color: var(--circle-color); /* Warna lingkaran dalam */ background-color: var(--circle-color); /* Warna lingkaran dalam */
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
@@ -57,17 +57,20 @@
padding: 0 7px; padding: 0 7px;
border-radius: 10px; border-radius: 10px;
margin: 0; margin: 0;
max-width: 100%;
} }
.circle-content .document-count { .circle-content .document-count {
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
margin: 0; margin: 0;
max-width: 100%;
} }
.circle-content .document-type { .circle-content .document-type {
font-size: 14px; font-size: 14px;
margin: 0; margin: 0;
max-width: 100%;
} }
.small-circle-container { .small-circle-container {

View File

@@ -0,0 +1,27 @@
@extends('layouts.vertical', ['subtitle' => 'Laporan Pimpinan'])
@section('css')
@vite(['node_modules/gridjs/dist/theme/mermaid.min.css'])
@endsection
@section('content')
@include('layouts.partials/page-title', ['title' => 'Laporan', 'subtitle' => 'Laporan Pimpinan'])
<x-toast-notification />
<div class="row">
<div class="col-12">
<div class="card w-100">
<div class="card-body">
<div id="table-bigdata-resumes"></div>
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
@vite(['resources/js/bigdata-resumes/index.js'])
@endsection

View File

@@ -64,7 +64,10 @@
<!-- end dropzon-preview --> <!-- end dropzon-preview -->
</div> </div>
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<button id="submit-upload" class="btn btn-primary">Upload Files</button> <button type="button" class="btn btn-primary" id="btnUploadBusinessIndustry">
<span id="spinner" class="spinner-border spinner-border-sm me-1 d-none" role="status" aria-hidden="true"></span>
Upload
</button>
</div> </div>
</div> <!-- end card body --> </div> <!-- end card body -->
</div> <!-- end card --> </div> <!-- end card -->

View File

@@ -9,7 +9,6 @@
@include('layouts.partials/page-title', ['title' => 'Data', 'subtitle' => 'Business Industries']) @include('layouts.partials/page-title', ['title' => 'Data', 'subtitle' => 'Business Industries'])
<x-toast-notification /> <x-toast-notification />
<x-modal-confirmation buttonText="Delete" confirmationMessage="Are you sure you want to delete this?" />
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">

View File

@@ -0,0 +1,92 @@
@extends('layouts.vertical', ['subtitle' => 'Chatbot'])
@section('css')
@vite(['node_modules/gridjs/dist/theme/mermaid.min.css'])
<style>
#user-message {
height: 60px; /* Menambah tinggi textarea */
font-size: 1.1rem; /* Memperbesar font */
padding: 10px; /* Menambah ruang di dalam textarea */
resize: none; /* Mencegah resize manual */
}
.loader {
width: 10px;
aspect-ratio: 1;
border-radius: 50%;
animation: l5 1s infinite linear alternate;
}
@keyframes l5 {
0% {box-shadow: 10px 0 #000, -10px 0 #0002; background: #000 }
33% {box-shadow: 10px 0 #000, -10px 0 #0002; background: #0002}
66% {box-shadow: 10px 0 #0002, -10px 0 #000; background: #0002}
100%{box-shadow: 10px 0 #0002, -10px 0 #000; background: #000 }
}
</style>
@endsection
@section('content')
@include('layouts.partials/page-title', ['title' => 'Chatbot', 'subtitle' => 'Chatbot'])
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs nav-justified">
<li class="nav-item">
<button id="count-retribusi" data-bs-toggle="tab" aria-expanded="false" class="nav-link">
<span class="d-block d-sm-none"><i class="bx bx-home"></i></span>
<span class="d-none d-sm-block">Perhitungan Retribusi</span>
</button>
</li>
<li class="nav-item">
<button id="document-validation" data-bs-toggle="tab" aria-expanded="true"
class="nav-link active">
<span class="d-block d-sm-none"><i class="bx bx-user"></i></span>
<span class="d-none d-sm-block">Validasi Dokumen PBG</span>
</button>
</li>
<li class="nav-item">
<button id="data-information" data-bs-toggle="tab" aria-expanded="false" class="nav-link">
<span class="d-block d-sm-none"><i class="bx bx-envelope"></i></span>
<span class="d-none d-sm-block">Pengumpulan Data PBG</span>
</button>
</li>
</ul>
</div>
<div class="card-body d-flex flex-column" style="height: 700px;">
<!-- Conversation Area -->
<!-- Bot Response -->
<div class="row flex-grow overflow-auto">
<div class="col-9 w-auto">
<span class="d-flex align-items-center mb-1">
<img class="rounded-circle" width="32" src="/images/iconchatbot.jpeg" alt="avatar-3">
</span>
<div class="bot-response p-2 bg-light rounded mb-2">
<p>Halo! Ada yang bisa saya bantu?</p>
</div>
</div>
</div>
<!-- Input & Button (Selalu di Bawah) -->
<div class="row mt-auto">
<div class="col-xl-12 d-flex align-items-end gap-1">
<textarea class="form-control" id="user-message"></textarea>
<button id="send" class="btn btn-primary btn-lg rounded-pill">
<i class='bx bx-send'></i>
</button>
</div>
{{-- <div class="col-xl-2 d-flex justify-content-end">
</div> --}}
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
@vite(['resources/js/chatbot/index.js'])
@endsection

View File

@@ -1,8 +1,8 @@
@extends('layouts.vertical', ['subtitle' => 'Data']) @extends('layouts.vertical', ['subtitle' => 'PDAM'])
@section('content') @section('content')
@include('layouts.partials/page-title', ['title' => 'Data', 'subtitle' => 'Tata Ruang']) @include('layouts.partials/page-title', ['title' => 'Data', 'subtitle' => 'PDAM'])
<x-toast-notification /> <x-toast-notification />
<div class="row"> <div class="row">

View File

@@ -9,7 +9,7 @@
@include('layouts.partials/page-title', ['title' => 'Dashboards', 'subtitle' => 'Dashboard Pimpinan']) @include('layouts.partials/page-title', ['title' => 'Dashboards', 'subtitle' => 'Dashboard Pimpinan'])
<div id="dashboard-fixed-wrapper" class="row"> <div id="dashboard-fixed-wrapper" class="row">
<div class="col-12"> <!-- <div class="col-12">
<h2 class="mt-3 ms-2 text-danger"> <h2 class="mt-3 ms-2 text-danger">
<span class="float-end fs-6 me-3 text-black d-block d-sm-inline text-end">Terakhir di update - {{$latest_created}}</span> <span class="float-end fs-6 me-3 text-black d-block d-sm-inline text-end">Terakhir di update - {{$latest_created}}</span>
ANALISA BIG DATA PROSES PBG <br> ANALISA BIG DATA PROSES PBG <br>
@@ -24,6 +24,18 @@
<input type="text" class="form-control" style="max-width: 125px;" id="datepicker-dashboard-bigdata" placeholder="Filter Date" /> <input type="text" class="form-control" style="max-width: 125px;" id="datepicker-dashboard-bigdata" placeholder="Filter Date" />
</div> </div>
</div> </div>
</div> -->
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mt-3 ms-2">
<h2 class="text-danger m-0">
ANALISA BIG DATA PROSES PBG <br>
MELALUI APLIKASI SIBEDAS PBG
</h2>
<div class="text-black text-end d-flex flex-column align-items-end me-3">
<span class="fs-5">Terakhir di update - {{$latest_created}}</span>
<input type="text" class="form-control mt-2" style="max-width: 125px;" id="datepicker-dashboard-bigdata" placeholder="Filter Date" />
</div>
</div>
</div> </div>
<div id="dashboard-fixed-container" class="row" style="width:1110px;height:770px;position:relative;margin:auto;"> <div id="dashboard-fixed-container" class="row" style="width:1110px;height:770px;position:relative;margin:auto;">
@component('components.circle', [ @component('components.circle', [
@@ -37,7 +49,7 @@
@endcomponent @endcomponent
@component('components.circle', [ @component('components.circle', [
'document_title' => 'Target PAD 2024', 'document_title' => 'Target PAD',
'document_color' => '#204f6b', 'document_color' => '#204f6b',
'document_type' => '', 'document_type' => '',
'document_id' => 'chart-target-pad', 'document_id' => 'chart-target-pad',

View File

@@ -9,23 +9,19 @@
<div class="lack-of-potential-wrapper"> <div class="lack-of-potential-wrapper">
<div class="row" id="lack-of-potential-wrapper"> <div class="row" id="lack-of-potential-wrapper">
<div class="col-12"> <div class="d-flex justify-content-between align-items-center mt-3 ms-2">
<h3 class="mt-3 ms-2 text-danger"> <h2 class="text-danger m-0">
ANALISA BIG DATA MELALUI APLIKASI SIBEDAS PBG ANALISA BIG DATA MELALUI APLIKASI SIBEDAS PBG
</h3> </h2>
<div class="text-black text-end d-flex flex-column align-items-end me-3">
<input type="text" class="form-control mt-2" style="max-width: 125px;" id="datepicker-lack-of-potential" placeholder="Filter Date" />
</div>
</div> </div>
</div> </div>
<div class="wrapper"> <div class="wrapper">
<div class="row d-flex justify-content-end">
<div class="col-12 col-sm-6 col-md-3">
<div class="d-flex flex-sm-nowrap flex-wrap justify-content-end">
<input type="text" class="form-control me-3" style="max-width: 125px;" id="datepicker-lack-of-potential" placeholder="Filter Date" />
</div>
</div>
</div>
<div id="lack-of-potential-fixed-container" class="" style="width:1400px;height:770px;position:relative;margin:auto;z-index:1;"> <div id="lack-of-potential-fixed-container" class="" style="width:1400px;height:770px;position:relative;margin:auto;z-index:1;">
<div style="position: absolute; top: 200px; left: 50px;"> <div style="position: absolute; top: 200px; left: 50px;">
<x-custom-circle title="Restoran" size="small" style="background-color: #0e4753;" /> <x-custom-circle title="Restoran" size="small" style="background-color: #0e4753;" visible_data="true" data_id="restoran-count" data_count="0" />
<div class="square dia-top-left-bottom-right" style="top:30px;left:50px;width:150px;height:120px;"></div> <div class="square dia-top-left-bottom-right" style="top:30px;left:50px;width:150px;height:120px;"></div>
<x-custom-circle title="PBB Bangunan" visible_data="true" data_id="pbb-bangunan-count" data_count="0" size="small" style="background-color: #0e4753;" /> <x-custom-circle title="PBB Bangunan" visible_data="true" data_id="pbb-bangunan-count" data_count="0" size="small" style="background-color: #0e4753;" />
<div class="square" style="width:150px;height:2px;background-color:black;left:50px;top:150px;"></div> <div class="square" style="width:150px;height:2px;background-color:black;left:50px;top:150px;"></div>
@@ -56,11 +52,11 @@
<div class="square dia-top-right-bottom-left" style="top:-110px;left:90px;width:150px;height:170px;"></div> <div class="square dia-top-right-bottom-left" style="top:-110px;left:90px;width:150px;height:170px;"></div>
<div class="square dia-top-left-bottom-right" style="top:-110px;left:230px;width:150px;height:170px;"></div> <div class="square dia-top-left-bottom-right" style="top:-110px;left:230px;width:150px;height:170px;"></div>
<div class="square dia-top-left-bottom-right" style="top:-110px;left:260px;width:200px;height:180px;"></div> <div class="square dia-top-left-bottom-right" style="top:-110px;left:260px;width:200px;height:180px;"></div>
<x-custom-circle title="Villa" size="small" style="float:left;background-color: #234f6c;" /> <x-custom-circle title="Villa" size="small" style="float:left;background-color: #234f6c;" visible_data="true" data_id="villa-count" data_count="0" />
<x-custom-circle title="Pabrik" size="small" style="float:left;background-color: #234f6c;" /> <x-custom-circle title="Pabrik" size="small" style="float:left;background-color: #234f6c;" />
<x-custom-circle title="Jalan Protocol" size="small" style="float:left;background-color: #234f6c;" /> <x-custom-circle title="Jalan Protocol" size="small" style="float:left;background-color: #234f6c;" />
<x-custom-circle title="Ruko" size="small" style="float:left;background-color: #234f6c;" /> <x-custom-circle title="Ruko" size="small" style="float:left;background-color: #234f6c;" />
<x-custom-circle title="Pariwisata" size="small" style="float:left;background-color: #234f6c; margin-right: 20px;" /> <x-custom-circle title="Pariwisata" size="small" style="float:left;background-color: #234f6c; margin-right: 20px;" visible_data="true" data_id="pariwisata-count" data_count="0" />
<div class="square" style="width:150px;height:2px;background-color:black;left:350px;top:50px;"></div> <div class="square" style="width:150px;height:2px;background-color:black;left:350px;top:50px;"></div>
<x-custom-circle title="DISBUDPAR" size="small" style="background-color: #3a968b;" /> <x-custom-circle title="DISBUDPAR" size="small" style="background-color: #3a968b;" />
</div> </div>
@@ -79,9 +75,7 @@
'style' => 'margin-left:180px;top:-20px;' 'style' => 'margin-left:180px;top:-20px;'
]) ])
@endcomponent @endcomponent
<x-custom-circle title="Tata Ruang" size="large" style="background-color: #da6635;float:left;margin-left:250px;" visible_data="true" data_id="tata-ruang-count" data_count="0" />
<x-custom-circle title="Tata Ruang" size="large" style="background-color: #da6635;float:left;margin-left:250px;" />
</div> </div>
<div style="position: absolute; top: 310px; left: 1150px;"> <div style="position: absolute; top: 310px; left: 1150px;">
@@ -89,7 +83,6 @@
<div class="square dia-top-right-bottom-left" style="top:-110px;left:-100px;width:100px;height:100px;"></div> <div class="square dia-top-right-bottom-left" style="top:-110px;left:-100px;width:100px;height:100px;"></div>
<x-custom-circle title="Peta" visible_data_type="true" data_type="1:5000" size="small" style="background-color: #224f6d;float:left;" /> <x-custom-circle title="Peta" visible_data_type="true" data_type="1:5000" size="small" style="background-color: #224f6d;float:left;" />
<x-custom-circle title="Tapak Bangunan" size="small" style="background-color: #2390af;float:left;margin-left:20px;" /> <x-custom-circle title="Tapak Bangunan" size="small" style="background-color: #2390af;float:left;margin-left:20px;" />
</div> </div>
<x-custom-circle title="BPN" size="small" style="background-color: #2390af;position:absolute;left:1270px;top:440px;" /> <x-custom-circle title="BPN" size="small" style="background-color: #2390af;position:absolute;left:1270px;top:440px;" />
@@ -109,7 +102,7 @@
<div style="position: absolute; top: 50px; left: 1100px;"> <div style="position: absolute; top: 50px; left: 1100px;">
<x-custom-circle title="Non Usaha" size="large" style="background-color: #3a968b;margin-top:20px;" /> <x-custom-circle title="Non Usaha" size="large" style="background-color: #3a968b;margin-top:20px;" />
<x-custom-circle title="USAHA" size="large" style="background-color: #627c8b;margin-top:260px;" /> <x-custom-circle title="USAHA" size="large" style="background-color: #627c8b;margin-top:260px;" visible_data="true" data_id="tata-ruang-usaha-count" data_count="0" />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -22,14 +22,20 @@
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<!-- Theme Color (Light/Dark) --> <!-- Theme Color (Light/Dark) -->
<!-- <div class="topbar-item"> {{-- <div class="topbar-item">
<button type="button" class="topbar-button" id="light-dark-mode"> <button type="button" class="topbar-button" id="light-dark-mode">
<iconify-icon icon="solar:moon-outline" <iconify-icon icon="solar:moon-outline"
class="fs-22 align-middle light-mode"></iconify-icon> class="fs-22 align-middle light-mode"></iconify-icon>
<iconify-icon icon="solar:sun-2-outline" <iconify-icon icon="solar:sun-2-outline"
class="fs-22 align-middle dark-mode"></iconify-icon> class="fs-22 align-middle dark-mode"></iconify-icon>
</button> </button>
</div> --> </div> --}}
<div class="topbar-item">
<a href="{{ route('chatbot.index') }}" class="topbar-button">
<iconify-icon icon="solar:chat-square-outline" class="fs-22 align-middle"></iconify-icon>
</a>
</div>
<!-- Notification --> <!-- Notification -->
<div class="dropdown topbar-item"> <div class="dropdown topbar-item">

View File

@@ -1,6 +1,61 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" @yield('html-attribute')> <html lang="en" @yield('html-attribute')>
<style>
/* .floating-icon {
position: fixed;
right: 40px;
bottom: 100px;
width: 70px;
height: 70px;
background: white;
border-radius: 50%;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
cursor: pointer;
z-index: 1000;
background-image: url('/images/iconchatbot.jpeg');
background-size: cover;
background-position: center;
} */
.floating-icon {
position: fixed;
right: 20px;
bottom: 20px;
width: 60px;
height: 60px;
background: white;
border-radius: 50%;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
cursor: pointer;
z-index: 1000;
background-image: url('/images/iconchatbot.jpeg');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
transition: transform 0.3s ease-in-out, box-shadow 0.3s ease-in-out;
}
.floating-icon:hover {
transform: scale(1.1);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
}
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
.floating-icon.animate {
animation: bounce 1s infinite;
}
</style>
<head> <head>
@include('layouts.partials/title-meta') @include('layouts.partials/title-meta')
@@ -17,10 +72,25 @@
<div class="page-content"> <div class="page-content">
<div class="container-fluid"> {{-- <div class="container-fluid">
@yield('content') @yield('content')
<div>
<iconify-icon icon="solar:chat-square-outline" class="fs-35 align-middle"></iconify-icon>
</div>
</div> --}}
<div class="container-fluid">
@yield('content')
{{-- <div class="floating-icon">
</div> --}}
@if (!Request::is('chatbot'))
<a href="{{ route('chatbot.index') }}" class="floating-icon">
</a>
@endif
</div> </div>
@include('layouts.partials/footer') @include('layouts.partials/footer')

View File

@@ -1,45 +1,41 @@
@extends('layouts.vertical', ['subtitle' => 'Google Maps']) @extends('layouts.vertical', ['subtitle' => 'Google Maps'])
@section('css') @section('css')
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> <style>
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5); /* Latar belakang gelap transparan */
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-text {
background: white;
padding: 10px 20px;
border-radius: 5px;
font-size: 18px;
font-weight: bold;
}
</style>
@endsection @endsection
@section('content') @section('content')
@include('layouts.partials/page-title', ['title' => 'Maps', 'subtitle' => 'Google Maps']) @include('layouts.partials.page-title', ['title' => 'Maps', 'subtitle' => 'Google Maps'])
<!-- Elemen loading -->
<div id="loading" class="loading-overlay">
<div class="loading-text">Loading data...</div>
</div>
<!-- Peta -->
<div id="map" style="width: 100%; height: 90vh;"></div> <div id="map" style="width: 100%; height: 90vh;"></div>
@endsection @endsection
@section('scripts') @section('scripts')
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> @vite(['resources/js/maps/maps-kml.js'])
<script src="https://unpkg.com/togeojson@0.16.0"></script>
<script src="https://unpkg.com/leaflet-omnivore@0.3.4/leaflet-omnivore.min.js"></script>
<script src="https://unpkg.com/leaflet-kml/L.KML.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
var map = L.map('map').setView([-6.9175, 107.6191], 10); // Jakarta
// Tambahkan peta dasar dari OpenStreetMap
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap contributors'
}).addTo(map);
// Muat file KMZ
omnivore.kml("{{ asset('storage/maps/rencanapolaruang_rtrw_2024_2044__rencana_pola_ruang.kml') }}")
.on('ready', function () {
try {
var bounds = this.getBounds();
if (bounds.isValid()) {
map.fitBounds(bounds);
} else {
console.warn("Bounds tidak valid, gunakan fallback.");
map.setView([-6.9175, 107.6191], 10); // Default ke Jakarta
}
} catch (error) {
console.error("Error setting bounds:", error);
map.setView([-6.1751, 106.8650], 10);
}
})
.addTo(map);
});
</script>
@endsection @endsection

View File

@@ -7,14 +7,16 @@
@section('content') @section('content')
@include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Syncronize']) @include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Syncronize'])
<x-toast-notification />
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card w-100"> <div class="card w-100">
<div class="card-body"> <div class="card-body">
<div class="d-flex flex-wrap justify-content-end gap-2"> <div class="d-flex flex-wrap justify-content-end gap-2">
<button type="button" class="btn btn-success btn-sm d-block d-sm-inline w-auto" id="btn-sync-submit-google-sheet">Sync Google Sheet</button> <button type="button" class="btn btn-success btn-sm d-block d-sm-inline w-auto" id="btn-sync-submit">
<button type="button" class="btn btn-success btn-sm d-block d-sm-inline w-auto" id="btn-sync-submit">Sync SIMBG</button> <span id="spinner" class="spinner-border spinner-border-sm me-1 d-none" role="status" aria-hidden="true"></span>
Sync SIMBG
</button>
</div> </div>
<div> <div>
<div id="table-import-datasources"></div> <div id="table-import-datasources"></div>

View File

@@ -21,9 +21,11 @@ use App\Http\Controllers\Api\AdvertisementController;
use App\Http\Controllers\Api\UmkmController; use App\Http\Controllers\Api\UmkmController;
use App\Http\Controllers\Api\TourismController; use App\Http\Controllers\Api\TourismController;
use App\Http\Controllers\Api\SpatialPlanningController; use App\Http\Controllers\Api\SpatialPlanningController;
use App\Http\Controllers\Api\ChatbotController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::post('/login', [UsersController::class, 'login'])->name('api.user.login'); Route::post('/login', [UsersController::class, 'login'])->name('api.user.login');
Route::post('/generate-text', [ChatbotController::class, 'generateText']);
Route::group(['middleware' => 'auth:sanctum'], function (){ Route::group(['middleware' => 'auth:sanctum'], function (){
// users // users
Route::controller(UsersController::class)->group(function(){ Route::controller(UsersController::class)->group(function(){
@@ -132,5 +134,8 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
Route::get('/dashboard-potential-count', [LackOfPotentialController::class, 'count_lack_of_potential'])->name('api.count-dashboard-potential'); Route::get('/dashboard-potential-count', [LackOfPotentialController::class, 'count_lack_of_potential'])->name('api.count-dashboard-potential');
// big data resume // big data resume
Route::get('/bigdata-resume', [BigDataResumeController::class, 'index'])->name('api.bigdata-resume'); Route::controller(BigDataResumeController::class)->group(function (){
Route::get('/bigdata-resume', 'index')->name('api.bigdata-resume');
Route::get('/bigdata-report', 'bigdata_report')->name('api.bigdata-report');
});
}); });

View File

@@ -1,5 +1,6 @@
<?php <?php
use App\Http\Controllers\BigdataResumesController;
use App\Http\Controllers\BusinessOrIndustriesController; use App\Http\Controllers\BusinessOrIndustriesController;
use App\Http\Controllers\CustomersController; use App\Http\Controllers\CustomersController;
use App\Http\Controllers\Dashboards\LackOfPotentialController; use App\Http\Controllers\Dashboards\LackOfPotentialController;
@@ -19,6 +20,7 @@ use App\Http\Controllers\Data\TourismController;
use App\Http\Controllers\Data\SpatialPlanningController; use App\Http\Controllers\Data\SpatialPlanningController;
use App\Http\Controllers\Report\ReportTourismController; use App\Http\Controllers\Report\ReportTourismController;
use App\Http\Controllers\SpatialPlanningsController; use App\Http\Controllers\SpatialPlanningsController;
use App\Http\Controllers\Chatbot\ChatbotController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
require __DIR__ . '/auth.php'; require __DIR__ . '/auth.php';
@@ -58,6 +60,9 @@ Route::group(['middleware' => 'auth'], function(){
// menus // menus
Route::resource('/menus', MenusController::class); Route::resource('/menus', MenusController::class);
// chatbot
Route::resource('/chatbot', ChatbotController::class);
// roles // roles
Route::resource('/roles', RolesController::class); Route::resource('/roles', RolesController::class);
Route::group(['prefix' => '/roles'], function (){ Route::group(['prefix' => '/roles'], function (){
@@ -108,5 +113,9 @@ Route::group(['middleware' => 'auth'], function(){
Route::group(['prefix' => '/report'], function(){ Route::group(['prefix' => '/report'], function(){
// Resource route, kecuali create karena dibuat terpisah // Resource route, kecuali create karena dibuat terpisah
Route::resource('/tourisms-report', ReportTourismController::class); Route::resource('/tourisms-report', ReportTourismController::class);
Route::controller(BigdataResumesController::class)->group(function (){
Route::get('/bigdata-resumes', 'index')->name('bigdata-resumes');
});
}); });
}); });

View File

@@ -3,10 +3,6 @@ import laravel from "laravel-vite-plugin";
import path from "path"; import path from "path";
export default defineConfig({ export default defineConfig({
build: {
outDir: "public/build",
manifest: true, // Menghasilkan manifest.json untuk Laravel
},
resolve: { resolve: {
alias: { alias: {
"@": path.resolve(__dirname, "resources/js"), "@": path.resolve(__dirname, "resources/js"),
@@ -98,6 +94,12 @@ export default defineConfig({
"resources/js/customers/index.js", "resources/js/customers/index.js",
"resources/js/customers/create.js", "resources/js/customers/create.js",
"resources/js/customers/edit.js", "resources/js/customers/edit.js",
"resources/js/dashboards/pbg.js",
// maps
"resources/js/maps/maps-kml.js",
// laporan pimpinan
"resources/js/bigdata-resumes/index.js",
"resources/js/chatbot/index.js",
], ],
refresh: true, refresh: true,
}), }),