Compare commits

...

43 Commits

Author SHA1 Message Date
arifal
c529a5d511 add readme 2025-03-07 14:39:23 +07:00
arifal
ff244039ff add readme and env example 2025-03-07 14:37:26 +07:00
arifal
55902042f4 add readme 2025-03-07 14:27:49 +07:00
arifal
c67aa979c2 change column type expertise and fix syncronize simbg service 2025-03-07 14:04:37 +07:00
arifal
fbaa33ae13 fix height scrollbar table grid js 2025-03-07 01:55:46 +07:00
arifal
9516b6f575 fix task assignment table on pbg task 2025-03-07 01:03:22 +07:00
arifal
ffc08f26cc add dashboard inside and outside system and fix timeout when search filter 2025-03-06 23:33:31 +07:00
arifal
e0c35b8897 fix search filter on page big data resume 2025-03-06 14:39:36 +07:00
arifal
22ee7502ad Merge remote-tracking branch 'origin/feature/chatbot-sidebar' into fix/sync-task-assignment 2025-03-06 11:45:34 +07:00
arifal
2f3bc172eb add search filter 2025-03-06 11:42:59 +07:00
@jamaludinarifrohman6661
bba932b2ba Merge remote-tracking branch 'origin/dev' into feature/chatbot-sidebar 2025-03-06 11:20:45 +07:00
@jamaludinarifrohman6661
3f5d0eb1cd fix: inserting chat history into the answer generation process 2025-03-06 11:06:45 +07:00
arifal
1f33d0de4e add sync task assignment pbg 2025-03-06 00:13:13 +07:00
arifal
86d694bcac add new menu chat bedas and view 2025-03-04 17:56:30 +07:00
arifal
cb5a3243fc Merge remote-tracking branch 'origin/feature/chatbot-sidebar' into dev 2025-03-04 17:32:37 +07:00
@jamaludinarifrohman6661
15210a56ee feature: chatbot pimpinan 2025-03-04 17:31:40 +07:00
arifal
a08f2cb2b7 fix optimizing deployment 2025-03-04 16:45:32 +07:00
arifal
632433c496 fix routing spatial-plannings 2025-03-04 16:23:00 +07:00
arifal
5b4780495e fix routing spatial-plannings 2025-03-04 16:17:47 +07:00
arifal
0a7012a57c fix tourisms routing 2025-03-04 16:13:17 +07:00
arifal
435a19346b fix route umkm 2025-03-04 16:05:21 +07:00
arifal
8fcf8859d6 hot fix advertisement route conflict 2025-03-04 15:58:30 +07:00
arifal
43a246d234 add permission deployment file 2025-03-04 08:49:51 +00:00
arifal
d6d0acf8fb hot fix conflict routing web and api advertisements 2025-03-04 15:46:35 +07:00
arifal
b4ec7a9d25 merge chatbot sidebar 2025-03-04 15:19:11 +07:00
arifal
5203babe11 Merge remote-tracking branch 'origin/feature/chatbot-sidebar' into dev 2025-03-04 15:15:32 +07:00
arifal
c0faafdbd7 hot fix add time midnight scheduler 2025-03-04 15:13:33 +07:00
arifal
c5e3fdd915 create file automation deployment using sh 2025-03-04 15:05:13 +07:00
@jamaludinarifrohman6661
572b86299c add:setting main chatbot
fix:chatbot ui
2025-03-04 14:46:29 +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
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
107 changed files with 7395 additions and 4732 deletions

View File

@@ -67,4 +67,5 @@ AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
API_KEY_GOOGLE="xxxxx"
SPREAD_SHEET_ID="xxxxx"
SPREAD_SHEET_ID="xxxxx"
OPENAI_API_KEY="xxxxx"

130
README.md
View File

@@ -1,66 +1,106 @@
<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">
<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>
search or pick icon in <a href="https://icon-sets.iconify.design/mingcute/?keyword=mingcute">here</a>
## 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).
- 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).
```
sudo apt update && sudo apt install supervisor -y
```
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 /home/arifal/development/sibedas-pbg-web/artisan queue:work --queue=default --timeout=40000 --tries=1 --sleep=3
autostart=true
autorestart=true
numprocs=4
redirect_stderr=true
stdout_logfile=/home/arifal/development/sibedas-pbg-web/storage/logs/worker.log
stopasgroup=true
killasgroup=true
```
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
sudo supervisorctl update
sudo supervisorctl start laravel-worker
sudo supervisorctl restart laravel-worker
sudo supervisorctl status
```
## Laravel Sponsors
# How to running
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).
- Install composer package
### Premium Partners
```
composer install
```
- **[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)**
- Install npm package
## Contributing
```
npm install && npm run build
```
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
- Create symlinks storage
## Code of Conduct
```
php artisan storage:link
```
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).
- Running migration
## Security Vulnerabilities
```
php artisan migrate
```
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.
- Create view table
- excute all sql queries on folder database/view_query
## License
# Add ENV variable
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
- API_KEY_GOOGLE
```
Get api key from google developer console for and turn on spreadsheet api or feaature for google sheet
```
- SPREAD_SHEET_ID
```
Get spreadsheet id from google sheet link
```
- OPENAI_API_KEY
```
Get OpenAI API key from chatgpt subscription
```
- ENV
```
API_KEY_GOOGLE="xxxxx"
SPREAD_SHEET_ID="xxxxx"
OPENAI_API_KEY="xxxxx"
```
# Technology version
- php 8.3
- Laravel 11
- node v22.13.0
- npm 10.9.2
- mariadb Ver 15.1 Distrib 10.6.18-MariaDB, for debian-linux-gnu (x86_64) using EditLine wrapper
- Ubuntu 24.04

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ use App\Models\BusinessOrIndustry;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
use \Illuminate\Support\Facades\Validator;
use App\Http\Requests\ExcelUploadRequest;
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){
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([
'message' => 'File validation failed.',
'errors' => $validator->errors()
], 400);
}
public function upload(ExcelUploadRequest $request){
try {
// Ambil file dari request
$file = $request->file('file');
if(!$request->hasFile('file')){
return response()->json([
'error' => 'No file provided'
], 400);
}
// Menggunakan Laravel Excel untuk mengimpor file
$file = $request->file('file');
Excel::import(new BusinessIndustriesImport, $file);
// Jika sukses, kembalikan respons sukses

View File

@@ -7,6 +7,7 @@ use App\Services\OpenAIService;
use App\Http\Controllers\Controller;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ChatbotController extends Controller
{
@@ -19,7 +20,6 @@ class ChatbotController extends Controller
public function generateText(Request $request)
{
info($request);
$request->validate([
'tab_active' => 'required|string',
'prompt' => 'required|string',
@@ -33,52 +33,87 @@ class ChatbotController extends Controller
default => "UNKNOWN",
};
$chatHistory = $request->input('chatHistory');
// Log::info('Chat history sebelum disimpan:', ['history' => $chatHistory]);
if ($main_content === "UNKNOWN") {
return response()->json(['response' => 'Invalid tab_active value.'], 400);
}
info($main_content);
// info($main_content);
// Klasifikasi apakah pertanyaan butuh database atau bisa dijawab langsung
$classifyResponse = $this->openAIService->generateClassifyMainContent($request->input('prompt'), $main_content);
$queryResponse = $this->openAIService->generateQueryBasedMainContent($request->input('prompt'), $main_content, $chatHistory);
$firstValidation = $this->openAIService->validateSyntaxQuery($queryResponse);
$secondValidation = $this->openAIService->validateSyntaxQuery($queryResponse);
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);
$formattedResultQuery = "[]";
$queryResponse = str_replace(['```sql', '```'], '', $queryResponse);
$resultQuery = DB::select($queryResponse);
$formattedResultQuery = json_encode($resultQuery, JSON_PRETTY_PRINT);
// info($formattedResultQuery);
$nlpResult = $this->openAIService->generateNLPFromQuery($request->input('prompt'), $formattedResultQuery);
$finalGeneratedText =$this->openAIService->generateFinalText($nlpResult);
return response()->json(['response' => $finalGeneratedText, 'nlpResponse' => $queryResponse]);
}
public function mainGenerateText(Request $request)
{
// Log hanya data yang relevan
info("Received prompt: " . $request->input('prompt'));
// Validasi input
$request->validate([
'prompt' => 'required|string',
]);
try {
// Panggil service untuk generate text
$classifyResponse = $this->openAIService->classifyMainGenerateText($request->input('prompt'));
info($classifyResponse);
// Pastikan hasil klasifikasi valid sebelum melanjutkan
$validCategories = [
'reklame', 'business_or_industries', 'customers',
'pbg', 'retribusi', 'spatial_plannings',
'tourisms', 'umkms'
];
if (!in_array($classifyResponse, $validCategories)) {
return response()->json([
'error' => ''
], 400);
}
$chatHistory = $request->input('chatHistory');
Log::info('Chat history sebelum disimpan:', ['history' => $chatHistory]);
$queryResponse = $this->openAIService->createMainQuery($classifyResponse, $request->input('prompt'), $chatHistory);
info($queryResponse);
$firstValidation = $this->openAIService->validateSyntaxQuery($queryResponse);
$secondValidation = $this->openAIService->validateSyntaxQuery($queryResponse);
$formattedResultQuery = "[]";
// 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);
$queryResponse = str_replace(['```sql', '```'], '', $queryResponse);
$queryResult = DB::select($queryResponse);
$formattedResultQuery = json_encode($queryResult, 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' => $finalGeneratedText, 'nlpResponse' => $queryResponse]);
} catch (\Exception $e) {
// Tangani error dan log exception
\Log::error("Error generating text: " . $e->getMessage());
return response()->json([
'error' => ''
], 500);
}
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'))) {
$query = $query->where('nomor_pelanggan', '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

@@ -34,7 +34,7 @@ class GlobalSettingsController extends Controller
try {
$data = GlobalSetting::create($request->validated());
return new GlobalSettingResource($data);
} catch (\Exception $e) {
} catch (Exception $e) {
return $this->resError($e->getMessage(), null, $e->getCode());
}
}

View File

@@ -24,7 +24,7 @@ class ImportDatasourceController extends Controller
$search = $request->get("search");
$query->where('status', 'like', "%".$search."%");
}
return ImportDatasourceResource::collection($query->paginate());
return ImportDatasourceResource::collection($query->paginate(config('app.paginate_per_page', 50)));
}
public function checkImportDatasource(){

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\TaskAssignmentsResource;
use App\Models\TaskAssignment;
use Illuminate\Http\Request;
class TaskAssignmentsController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request, $uuid)
{
try{
$query = TaskAssignment::query()
->where('pbg_task_uid', $uuid)
->orderBy('id', 'desc');
if ($request->filled('search')) {
$query->where('name', 'like', "%{$request->get('search')}%")
->orWhere('email', 'like', "%{$request->get('search')}%");
}
return TaskAssignmentsResource::collection($query->paginate(config('app.paginate_per_page', 50)));
}catch(\Exception $exception){
return response()->json(['message' => $exception->getMessage()], 500);
}
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
}
}

View File

@@ -29,9 +29,10 @@ class UsersController extends Controller
public function index(Request $request){
$query = User::query();
if($request->has('search') && !empty($request->get("search"))){
$query->where('name', 'LIKE', '%'.$request->get('search').'%');
$query->where('name', 'LIKE', '%'.$request->get('search').'%')
->orWhere('email', 'LIKE', '%'.$request->get('search').'%');
}
return UserResource::collection($query->paginate());
return UserResource::collection($query->paginate(config('app.paginate_per_page', 50)));
}
public function logout(Request $request){
$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\ChatbotPimpinan;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ChatbotPimpinanController extends Controller
{
/**
* Display a listing of the resource
*/
public function index()
{
return view('chatbot-pimpinan.index');
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Http\Controllers\Dashboards;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class PotentialsController extends Controller
{
public function inside_system(){
return view('dashboards.potentials.inside_system');
}
public function outside_system(){
return view('dashboards.potentials.outside_system');
}
}

View File

@@ -62,7 +62,7 @@ class AdvertisementController extends Controller
// Pastikan model ditemukan
if (!$modelInstance) {
info("AdvertisementController@edit: Model tidak ditemukan.");
return redirect()->route('advertisements.index')->with('error', 'Advertisement not found');
return redirect()->route('web.advertisements.index')->with('error', 'Advertisement not found');
}
// Mengambil dan memetakan village_name dan district_name

View File

@@ -58,7 +58,7 @@ class TourismController extends Controller
$modelInstance = Tourism::find($id);
// Pastikan model ditemukan
if (!$modelInstance) {
return redirect()->route('tourisms.index') ->with('error', 'Pariwisata tidak ditemukan');
return redirect()->route('web-tourisms.index') ->with('error', 'Pariwisata tidak ditemukan');
}
// Mengambil dan memetakan village_name dan district_name

View File

@@ -60,7 +60,7 @@ class UmkmController extends Controller
$modelInstance = Umkm::find($id);
// Pastikan model ditemukan
if (!$modelInstance) {
return redirect()->route('umkm.index')->with('error', 'Umkm not found');
return redirect()->route('web-umkm.index')->with('error', 'Umkm not found');
}
// Mengambil dan memetakan village_name dan district_name

View File

@@ -17,12 +17,12 @@ class SyncronizeController extends Controller
}
public function syncPbgTask(){
$res = $this->service_simbg->syncTaskList();
$res = $this->service_simbg->syncTaskPBG();
return $res;
}
public function syncronizeTask(Request $request){
$res = $this->service_simbg->syncTaskList();
$res = $this->service_simbg->syncTaskPBG();
return redirect()->back()->with('success', 'Processing completed successfully');
}
@@ -33,7 +33,7 @@ class SyncronizeController extends Controller
public function syncIndexIntegration(Request $request, $uuid){
$token = $request->get('token');
$res = $this->service_simbg->syncIndexIntegration($uuid, $token);
$res = $this->service_simbg->syncIndexIntegration($uuid);
return $res;
}
@@ -42,4 +42,9 @@ class SyncronizeController extends Controller
$res = $this->service_simbg->syncTaskDetailSubmit($uuid, $token);
return $res;
}
public function syncTaskAssignments($uuid){
$res = $this->service_simbg->syncTaskAssignments($uuid);
return $res;
}
}

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

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class TaskAssignmentsResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return parent::toArray($request);
}
}

View File

@@ -3,37 +3,105 @@
namespace App\Imports;
use App\Models\BusinessOrIndustry;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\ToCollection;
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
*
* @return \Illuminate\Database\Eloquent\Model|null
*/
public function collection(Collection $rows)
* @param Collection $collection
*/
public function collection(Collection $collection)
{
foreach ($rows->skip(1) as $row){
$clean_nop = preg_replace('/[^A-Za-z0-9]/', '', $row[2]);
if (!BusinessOrIndustry::where('nop', $clean_nop)->exists()) {
BusinessOrIndustry::create([
'nama_kecamatan' => $row[0],
'nama_kelurahan' => $row[1],
'nop' => $clean_nop, // Store cleaned 'nop'
'nama_wajib_pajak' => $row[3],
'alamat_wajib_pajak' => $row[4],
'alamat_objek_pajak' => $row[5],
'luas_bumi' => $row[6],
'luas_bangunan' => $row[7],
'njop_bumi' => $row[8],
'njop_bangunan' => $row[9],
'ketetapan' => $row[10],
'tahun_pajak' => $row[11],
try{
$batchData = [];
$batchSize = 1000;
foreach ($collection as $row){
if(!isset($row['nop']) || empty($row['nop'])){
continue;
}
$clean_nop = preg_replace('/[^A-Za-z0-9]/', '', $row['nop']);
$batchData[] = [
'nama_kecamatan' => $row['nama_kecamatan'],
'nama_kelurahan' => $row['nama_kelurahan'],
'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,32 +11,61 @@ 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 CustomersImport implements ToCollection, WithMultipleSheets
class CustomersImport implements ToCollection, WithMultipleSheets, WithChunkReading, WithBatchInserts, ShouldQueue, WithHeadingRow
{
/**
* @param Collection $collection
*/
*/
public function collection(Collection $collection)
{
$batchData = [];
$batchSize = 1000;
foreach ($collection->skip(1) as $row) {
if (!isset($row[0]) || empty($row[0])) {
foreach ($collection as $row) {
if (!isset($row['nomor_pelanggan']) || empty($row['nomor_pelanggan'])) {
continue;
}
$latitude = filter_var($row[4], FILTER_VALIDATE_FLOAT) ? bcadd($row[4], '0', 18) : null;
$longitude = filter_var($row[5], FILTER_VALIDATE_FLOAT) ? bcadd($row[5], '0', 18) : null;
$latitude = '0';
$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[] = [
'nomor_pelanggan' => $row[0],
'kota_pelayanan' => $row[1],
'nama' => $row[2],
'alamat' => $row[3],
'nomor_pelanggan' => $row['nomor_pelanggan'] ?? '',
'kota_pelayanan' => $row['kota_pelayanan'] ?? '',
'nama' => $row['nama'] ?? '',
'alamat' => $row['alamat'] ?? '',
'latitude' => $latitude,
'longitude' => $longitude,
];
if (count($batchData) >= $batchSize) {
Customer::upsert($batchData, ['nomor_pelanggan'], ['kota_pelayanan', 'nama', 'alamat', 'latitude', 'longitude']);
$batchData = [];
}
}
if (!empty($batchData)) {
@@ -44,9 +73,20 @@ class CustomersImport implements ToCollection, WithMultipleSheets
}
}
public function sheets(): array {
return [
0 => $this
];
}
public function chunkSize(): int
{
return 1000;
}
public function batchSize(): int
{
return 1000;
}
}

View File

@@ -0,0 +1,35 @@
<?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 $tries = 1;
public function __construct()
{
}
public function handle(): void
{
try {
$serviceSIMBG = app(ServiceSIMBG::class);
$serviceSIMBG->syncTaskPBG();
} catch (\Exception $e) {
\Log::error("SyncronizeSIMBG Job Failed: " . $e->getMessage(), [
'exception' => $e,
]);
$this->fail($e); // Mark the job as failed
}
}
}

View File

@@ -21,6 +21,15 @@ class BigdataResume extends Model
'business_sum',
'non_business_count',
'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()
@@ -28,88 +37,84 @@ class BigdataResume extends Model
return $this->belongsTo(ImportDatasource::class, 'import_datasource_id');
}
public static function generateResumeData($import_datasource_id){
$query_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')
->whereRaw('LOWER(TRIM(ptgs.status_verifikasi)) = ?', [strtolower(trim('Selesai Verifikasi'))])
->selectRaw('COUNT(pt.id) AS total_data,
SUM(ptr.nilai_retribusi_bangunan) AS total_retribution')
->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
public static function generateResumeData($import_datasource_id, $year, $data_setting){
$stats = PbgTask::with(['googleSheet', 'pbg_task_retributions'])
->leftJoin('pbg_task_retributions as ptr', 'pbg_task.uuid', '=', 'ptr.pbg_task_uid')
->leftJoin('pbg_task_google_sheet as ptgs', 'pbg_task.registration_number', '=', 'ptgs.no_registrasi')
->when($year !== 'all', function ($query) use ($year) {
$query->whereYear('pbg_task.task_created_at', (int) $year);
})
->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;
->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,
$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
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();
// 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();
});
$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_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([
'import_datasource_id' => $import_datasource_id,
'spatial_count' => $spatial_planning_count,
'spatial_sum' => $spatial_planning_total ?? 0.00,
'potention_count' => $potention_count ?? 0,
'potention_sum' => $potention_total ?? 0.00,
'non_verified_count' => $non_verified_count ?? 0,
@@ -120,6 +125,13 @@ class BigdataResume extends Model
'business_sum' => $business_total ?? 0.00,
'non_business_count' => $non_business_count ?? 0,
'non_business_sum' => $non_business_total ?? 0.00,
'year' => $year,
'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,13 @@ class PbgTask extends Model
public function pbg_task_index_integrations(){
return $this->hasOne(PbgTaskIndexIntegrations::class, 'pbg_task_uid', 'uuid');
}
public function googleSheet(){
return $this->hasOne(PbgTaskGoogleSheet::class, 'no_registrasi', 'registration_number');
}
public function taskAssignments()
{
return $this->hasMany(TaskAssignment::class, 'pbg_task_uid', 'uuid');
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class TaskAssignment extends Model
{
use HasFactory;
protected $fillable = [
'user_id', 'name', 'username', 'email', 'phone_number', 'role',
'role_name', 'is_active', 'file', 'expertise', 'experience',
'is_verif', 'uid', 'status', 'status_name', 'note', 'pbg_task_uid', 'tas_id'
];
protected $casts = [
'is_active' => 'boolean',
'is_verif' => 'boolean',
'file' => 'array', // JSON field casting
];
public function pbgTask()
{
return $this->belongsTo(PbgTask::class, 'pbg_task_uid', 'uuid');
}
}

View File

@@ -9,6 +9,8 @@ use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Carbon\Carbon;
use App\Services\ServiceSIMBG;
use App\Services\GoogleSheetService;
class AppServiceProvider extends ServiceProvider
{
@@ -17,7 +19,12 @@ class AppServiceProvider extends ServiceProvider
*/
public function register(): void
{
//
$this->app->singleton(GoogleSheetService::class, function () {
return new GoogleSheetService();
});
$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 $service;
protected $spreadsheetID;
protected $service_sheets;
public function __construct()
{
$this->client = new Google_Client();

View File

@@ -4,6 +4,7 @@ namespace App\Services;
use OpenAI;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
class OpenAIService
{
@@ -11,54 +12,11 @@ class OpenAIService
public function __construct()
{
// $this->client = OpenAI::client(env('OPENAI_API_KEY'));
$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)
public function generateQueryBasedMainContent($prompt, $mainContent, $chatHistory)
{
// Load file JSON
$jsonPath = public_path('templates/contentTemplatePrompt.json'); // Sesuaikan path
@@ -72,17 +30,59 @@ class OpenAIService
// Ambil template berdasarkan kategori
$promptTemplate = $jsonData[$mainContent]['prompt'];
// Menyusun pesan untuk OpenAI
$messages = [
['role' => 'system', 'content' => $promptTemplate],
];
// Menambahkan chat history sebagai konteks
foreach ($chatHistory as $chat) {
if (isset($chat['user'])) {
$messages[] = ['role' => 'user', 'content' => $chat['user']];
}
if (isset($chat['rawBotResponse'])) {
$messages[] = ['role' => 'assistant', 'content' => $chat['rawBotResponse']];
}
}
// Tambahkan prompt terbaru user
$messages[] = ['role' => 'user', 'content' => $prompt];
// Kirim request ke OpenAI API
$response = $this->client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'system', 'content' => $promptTemplate],
['role' => 'user', 'content' => $prompt],
],
'messages' => $messages,
]);
return trim($response['choices'][0]['message']['content'] ?? 'No response');
}
// public function generateQueryBasedMainContent($prompt, $mainContent, $chatHistory)
// {
// // 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)
{
@@ -152,6 +152,125 @@ class OpenAIService
]);
return trim($response['choices'][0]['message']['content'] ?? 'No response');
}
}
public function classifyMainGenerateText($prompt) {
$response = $this->client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
[
'role' => 'system',
'content' => "You are an assistant that classifies text into one of the following categories:
- reklame (ads or product/service promotions)
- business_or_industries (business or industries in general)
- customers (customers, consumers, or service users)
- pbg (tasks related to Building Approval)
- retribusi (retributions related to PBG)
- spatial_plannings (spatial planning)
- tourisms (tourism and tourist destinations)
- umkms (Micro, Small, and Medium Enterprises)
Respond with only one of the categories above without any additional explanation."
],
[
'role' => 'user',
'content' => "Classify the following text:\n\n" . $prompt
],
],
]);
return trim($response['choices'][0]['message']['content'] ?? 'No response');
}
public function createMainQuery($classify, $prompt, $chatHistory)
{
// Load file JSON
$jsonPath = public_path('templates/table_config.json');
$jsonConfig = json_decode(file_get_contents($jsonPath), true);
// Pastikan kategori tersedia dalam konfigurasi
if (!isset($jsonConfig[$classify])) {
return "Error: Kategori tidak ditemukan dalam konfigurasi.";
}
// Ambil nama tabel dan kolom
$tableName = $jsonConfig[$classify]['table_name'];
$columns = implode(', ', $jsonConfig[$classify]['list_column']);
// Konversi chatHistory ke dalam format messages
$messages = [
[
'role' => 'system',
'content' => "You are an AI assistant that generates only valid MariaDB queries based on user requests.
Use the following table information to construct the SQL query:
- Table Name: $tableName
- Available Columns: $columns
Generate only the SQL query without any explanation or additional text.
The query should include `LIMIT 10` to restrict the results."
]
];
// Menambahkan chat history sebagai konteks
foreach ($chatHistory as $chat) {
if (isset($chat['user'])) {
$messages[] = ['role' => 'user', 'content' => $chat['user']];
}
if (isset($chat['rawBotResponse'])) {
$messages[] = ['role' => 'assistant', 'content' => $chat['rawBotResponse']];
}
}
// Tambahkan prompt utama pengguna
$messages[] = ['role' => 'user', 'content' => $prompt];
// Kirim permintaan ke model AI
$response = $this->client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => $messages
]);
return trim($response['choices'][0]['message']['content'] ?? 'No response');
}
// public function createMainQuery($classify, $prompt)
// {
// // Load file JSON
// $jsonPath = public_path('templates/table_config.json');
// $jsonConfig = json_decode(file_get_contents($jsonPath), true);
// // Pastikan kategori tersedia dalam konfigurasi
// if (!isset($jsonConfig[$classify])) {
// return "Error: Kategori tidak ditemukan dalam konfigurasi.";
// }
// // Ambil nama tabel dan kolom
// $tableName = $jsonConfig[$classify]['table_name'];
// $columns = implode(', ', $jsonConfig[$classify]['list_column']);
// $response = $this->client->chat()->create([
// 'model' => 'gpt-4o-mini',
// 'messages' => [
// [
// 'role' => 'system',
// 'content' => "You are an AI assistant that generates only valid MariaDB queries based on user requests.
// Use the following table information to construct the SQL query:
// - Table Name: $tableName
// - Available Columns: $columns
// Generate only the SQL query without any explanation or additional text
// The query should include `LIMIT 10` to restrict the results."
// ],
// [
// 'role' => 'user',
// 'content' => $prompt
// ],
// ],
// ]);
// return trim($response['choices'][0]['message']['content'] ?? 'No response');
// }
}

View File

@@ -51,10 +51,26 @@ class ServiceClient
$resultResponse = json_decode($responseBody, true, 512, JSON_THROW_ON_ERROR);
return $this->resSuccess($resultResponse);
} catch (Exception $e) {
\Log::error('error from client service'. $e->getMessage());
return $this->resError($e->getMessage());
}
} catch (\GuzzleHttp\Exception\ClientException $e) {
// Handle 4xx errors (e.g., 401 Unauthorized)
$responseBody = (string) $e->getResponse()->getBody();
$errorResponse = json_decode($responseBody, true);
if (isset($errorResponse['code']) && $errorResponse['code'] === 'token_not_valid') {
return $this->resError('Invalid token, please refresh your token.', $errorResponse, 401);
}
return $this->resError('Client error from API', $errorResponse, $e->getResponse()->getStatusCode());
} catch (\GuzzleHttp\Exception\ServerException $e) {
// Handle 5xx errors (e.g., Internal Server Error)
return $this->resError('Server error from API', (string) $e->getResponse()->getBody(), 500);
} catch (\GuzzleHttp\Exception\RequestException $e) {
// Handle network errors (e.g., timeout, connection issues)
return $this->resError('Network error: ' . $e->getMessage(), null, 503);
} catch (Exception $e) {
// Handle unexpected errors
return $this->resError('Unexpected error: ' . $e->getMessage(), null, 500);
}
}
// Fungsi untuk melakukan permintaan GET

View File

@@ -9,12 +9,16 @@ use App\Models\ImportDatasource;
use App\Models\PbgTaskIndexIntegrations;
use App\Models\PbgTaskPrasarana;
use App\Models\PbgTaskRetributions;
use App\Models\TaskAssignment;
use Exception;
use App\Models\PbgTask;
use App\Traits\GlobalApiResponse;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
use App\Services\ServiceClient;
use App\Services\GoogleSheetService;
use App\Models\DataSetting;
use App\Models\PbgTaskGoogleSheet;
class ServiceSIMBG
{
@@ -24,10 +28,11 @@ class ServiceSIMBG
private $simbg_host;
private $fetch_per_page;
private $service_client;
private $googleSheetService;
/**
* Create a new class instance.
*/
public function __construct()
public function __construct(GoogleSheetService $googleSheetService)
{
$settings = GlobalSetting::whereIn('key', [
'SIMBG_EMAIL', 'SIMBG_PASSWORD', 'SIMBG_HOST', 'FETCH_PER_PAGE'
@@ -39,6 +44,7 @@ class ServiceSIMBG
$this->fetch_per_page = trim((string) ($settings['FETCH_PER_PAGE'] ?? ""));
$this->service_client = new ServiceClient($this->simbg_host);
$this->googleSheetService = $googleSheetService;
}
public function getToken(){
@@ -61,12 +67,19 @@ class ServiceSIMBG
}
}
public function syncIndexIntegration($uuids, $token)
public function syncIndexIntegration($uuids)
{
try{
if(empty($uuids)){
return false;
}
$initResToken = $this->getToken();
if (empty($initResToken->original['data']['token']['access'])) {
Log::error("API response indicates failure", ['token' => 'Failed to retrieve token']);
return false;
}
$token = $initResToken->original['data']['token']['access'];
$integrations = [];
foreach($uuids as $uuid){
@@ -81,13 +94,13 @@ class ServiceSIMBG
if (empty($res->original['success']) || !$res->original['success']) {
// Log error
Log::error("API response indicates failure", ['url' => $url, 'uuid' => $uuid]);
return false;
continue;
}
$data = $res->original['data']['data'] ?? null;
if (!$data) {
Log::error("No valid data returned from API", ['url' => $url, 'uuid' => $uuid]);
return false;
continue;
}
$integrations[] = [
@@ -104,19 +117,6 @@ class ServiceSIMBG
PbgTaskIndexIntegrations::upsert($integrations, ['pbg_task_uid'], ['indeks_fungsi_bangunan',
'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;
}catch (Exception $e){
@@ -125,154 +125,366 @@ class ServiceSIMBG
}
}
public function syncTaskList()
public function syncTaskPBG()
{
$initResToken = $this->getToken();
$importDatasource = ImportDatasource::create([
'status' => ImportDatasourceStatus::Processing->value,
]);
if (empty($initResToken->original['data']['token']['access'])) {
$importDatasource->update([
'status' => ImportDatasourceStatus::Failed->value,
'message' => 'Failed to retrieve token'
try {
Log::info("Processing google sheet sync");
$importDatasource = ImportDatasource::create([
'status' => ImportDatasourceStatus::Processing->value,
]);
return $this->resError("Failed to retrieve token");
}
$apiToken = $initResToken->original['data']['token']['access'];
$headers = ['Authorization' => "Bearer " . $apiToken];
$url = "/api/pbg/v1/list/?page=1&size={$this->fetch_per_page}&sort=ASC";
$initialResponse = $this->service_client->get($url, $headers);
$totalPage = $initialResponse->original['data']['total_page'] ?? 0;
if ($totalPage == 0) {
$importDatasource->update([
'status' => ImportDatasourceStatus::Failed->value,
'message' => 'Invalid response: no total_page'
]);
return $this->resError("Invalid response from API");
}
$savedCount = $failedCount = 0;
for ($currentPage = 1; $currentPage <= $totalPage; $currentPage++) {
$pageUrl = "/api/pbg/v1/list/?page={$currentPage}&size={$this->fetch_per_page}&sort=ASC";
$getToken = $this->getToken();
Log::info("response index integration", ['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);
$tasks = $response->original['data']['data'] ?? [];
if (empty($tasks)) {
$importDatasource->update([
'status' => ImportDatasourceStatus::Failed->value,
'message' => 'No data found on page'
]);
Log::warning("No data found on page", ['page' => $currentPage]);
break;
}
Log::info("executed page", ['page' => $currentPage, 'total' => $totalPage]);
$tasksCollective = [];
foreach ($tasks as $item) {
try {
$tasksCollective[] = [
'uuid' => $item['uid'],
'name' => $item['name'],
'owner_name' => $item['owner_name'],
'application_type' => $item['application_type'],
'application_type_name' => $item['application_type_name'],
'condition' => $item['condition'],
'registration_number' => $item['registration_number'],
'document_number' => $item['document_number'],
'address' => $item['address'],
'status' => $item['status'],
'status_name' => $item['status_name'],
'slf_status' => $item['slf_status'] ?? null,
'slf_status_name' => $item['slf_status_name'] ?? null,
'function_type' => $item['function_type'],
'consultation_type' => $item['consultation_type'],
'due_date' => $item['due_date'],
'land_certificate_phase' => $item['land_certificate_phase'],
'task_created_at' => isset($item['created_at']) ? Carbon::parse($item['created_at'])->format('Y-m-d H:i:s') : null,
'updated_at' => now(),
'created_at' => now(),
];
// $this->syncIndexIntegration($item['uid'], $token);
$this->syncTaskDetailSubmit($item['uid'], $token);
$savedCount++;
} catch (Exception $e) {
$failedCount++;
$importDatasource->update([
'status' => ImportDatasourceStatus::Failed->value,
'message' => "Successfully processed: $savedCount, Failed: $failedCount"
]);
Log::error("Failed to process task", [
'error' => $e->getMessage(),
'task' => $item,
]);
break;
// 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"] = $this->convertToInteger($row[2]) ?? null;
$data_setting_result["MENUNGGU_KLIK_DPMPTSP_SUM"] = $this->convertToDecimal($row[3]) ?? null;
} elseif ($found_section === "REALISASI_TERBIT_PBG") {
$data_setting_result["REALISASI_TERBIT_PBG_COUNT"] = $this->convertToInteger($row[2]) ?? null;
$data_setting_result["REALISASI_TERBIT_PBG_SUM"] = $this->convertToDecimal($row[4]) ?? null;
} elseif ($found_section === "PROSES_DINAS_TEKNIS") {
$data_setting_result["PROSES_DINAS_TEKNIS_COUNT"] = $this->convertToInteger($row[2]) ?? null;
$data_setting_result["PROSES_DINAS_TEKNIS_SUM"] = $this->convertToDecimal($row[3]) ?? null;
}
// Reset section tracking after capturing "Grand Total"
$found_section = null;
}
}
PbgTask::upsert($tasksCollective, ['uuid'], [
'name', 'owner_name', 'application_type', 'application_type_name', 'condition',
'registration_number', 'document_number', 'address', 'status', 'status_name',
'slf_status', 'slf_status_name', 'function_type', 'consultation_type', 'due_date',
'land_certificate_phase', 'task_created_at', 'updated_at'
Log::info("data setting result", ['result' => $data_setting_result]);
foreach ($data_setting_result as $key => $value) {
DataSetting::updateOrInsert(
["key" => $key], // Find by key
["value" => $value] // Update or insert value
);
}
$mapToUpsert = [];
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'])) {
$importDatasource->update([
'status' => ImportDatasourceStatus::Failed->value,
'response_body' => 'Failed to retrieve token'
]);
return $this->resError("Failed to retrieve token");
}
$apiToken = $initResToken->original['data']['token']['access'];
$headers = ['Authorization' => "Bearer " . $apiToken];
$url = "/api/pbg/v1/list/?page=1&size={$this->fetch_per_page}&sort=ASC";
$initialResponse = $this->service_client->get($url, $headers);
$totalPage = $initialResponse->original['data']['total_page'] ?? 0;
if ($totalPage == 0) {
$importDatasource->update([
'status' => ImportDatasourceStatus::Failed->value,
'response_body' => 'Invalid response: no total_page'
]);
return $this->resError("Invalid response from API");
}
$savedCount = $failedCount = 0;
Log::info("Fetching tasks", ['total page' => $totalPage]);
for ($currentPage = 1; $currentPage <= $totalPage; $currentPage++) {
try {
$pageUrl = "/api/pbg/v1/list/?page={$currentPage}&size={$this->fetch_per_page}&sort=ASC";
Log::info("Fetching tasks", ['currentPage' => $currentPage]);
$headers = [
'Authorization' => "Bearer " . $apiToken, // Update headers
];
for ($attempt = 0; $attempt < 2; $attempt++) { // Try twice (original + retry)
$response = $this->service_client->get($pageUrl, $headers);
if ($response instanceof \Illuminate\Http\JsonResponse) {
$decodedResponse = json_decode($response->getContent(), true);
if (isset($decodedResponse['errors']['code']) && $decodedResponse['errors']['code'] === 'token_not_valid') {
$initResToken = $this->getToken();
if (!empty($initResToken->original['data']['token']['access'])) {
$new_token = $initResToken->original['data']['token']['access'];
$headers['Authorization'] = "Bearer " . $new_token;
continue;
} else {
Log::error("Failed to refresh token");
return $this->resError("Failed to refresh token");
}
}
}
// Success case, break loop
break;
}
$tasks = $response->original['data']['data'] ?? [];
if (empty($tasks)) {
Log::warning("No data found on page", ['page' => $currentPage]);
continue;
}
$tasksCollective = [];
foreach ($tasks as $item) {
try {
$tasksCollective[] = [
'uuid' => $item['uid'],
'name' => $item['name'],
'owner_name' => $item['owner_name'],
'application_type' => $item['application_type'],
'application_type_name' => $item['application_type_name'],
'condition' => $item['condition'],
'registration_number' => $item['registration_number'],
'document_number' => $item['document_number'],
'address' => $item['address'],
'status' => $item['status'],
'status_name' => $item['status_name'],
'slf_status' => $item['slf_status'] ?? null,
'slf_status_name' => $item['slf_status_name'] ?? null,
'function_type' => $item['function_type'],
'consultation_type' => $item['consultation_type'],
'due_date' => $item['due_date'],
'land_certificate_phase' => $item['land_certificate_phase'],
'task_created_at' => isset($item['created_at']) ? Carbon::parse($item['created_at'])->format('Y-m-d H:i:s') : null,
'updated_at' => now(),
'created_at' => now(),
];
$this->syncTaskDetailSubmit($item['uid'], $apiToken);
$this->syncTaskAssignments($item['uid']);
$savedCount++;
} catch (Exception $e) {
$failedCount++;
Log::error("Failed to process task", [
'error' => $e->getMessage(),
'task' => $item,
]);
continue; // Skip failed task, continue processing the rest
}
}
if (!empty($tasksCollective)) {
PbgTask::upsert($tasksCollective, ['uuid'], [
'name', 'owner_name', 'application_type', 'application_type_name', 'condition',
'registration_number', 'document_number', 'address', 'status', 'status_name',
'slf_status', 'slf_status_name', 'function_type', 'consultation_type', 'due_date',
'land_certificate_phase', 'task_created_at', 'updated_at'
]);
$uuids = array_column($tasksCollective, 'uuid');
$this->syncIndexIntegration($uuids);
}
} 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([
'status' => ImportDatasourceStatus::Success->value,
'message' => "Successfully processed: $savedCount, Failed: $failedCount"
]);
$uuids = array_column($tasksCollective, 'uuid');
$this->syncIndexIntegration($uuids, $token);
Log::info("syncTaskList completed", ['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,
'response_body' => 'Critical failure: ' . $e->getMessage()
]);
}
return $this->resError("Critical failure occurred: " . $e->getMessage());
}
$importDatasource->update([
'status' => ImportDatasourceStatus::Success->value,
'message' => "Successfully processed: $savedCount, Failed: $failedCount"
]);
BigdataResume::generateResumeData($importDatasource->id);
Log::info("syncTaskList completed", ['savedCount' => $savedCount, 'failedCount' => $failedCount]);
return $this->resSuccess(['savedCount' => $savedCount, 'failedCount' => $failedCount]);
}
public function syncTaskDetailSubmit($uuid, $token)
{
try{
$url = "/api/pbg/v1/detail/" . $uuid . "/retribution/submit/";
$headers = [
'Authorization' => "Bearer " . $token,
];
$res = $this->service_client->get($url, $headers);
if (empty($res->original['success']) || !$res->original['success']) {
// Log error
Log::error("API response indicates failure", ['url' => $url, 'uuid' => $uuid]);
return false;
for ($attempt = 0; $attempt < 2; $attempt++) {
$res = $this->service_client->get($url, $headers);
// Check if response is JsonResponse and decode it
if ($res instanceof \Illuminate\Http\JsonResponse) {
$decodedResponse = json_decode($res->getContent(), true);
if (isset($decodedResponse['errors']['code']) && $decodedResponse['errors']['code'] === 'token_not_valid') {
$initResToken = $this->getToken();
if (!empty($initResToken->original['data']['token']['access'])) {
$new_token = $initResToken->original['data']['token']['access'];
$headers['Authorization'] = "Bearer " . $new_token;
continue;
} else {
Log::error("Failed to refresh token");
return $this->resError("Failed to refresh token");
}
}
}
break;
}
$data = $res->original['data']['data'] ?? [];
// Ensure response is valid before accessing properties
$responseData = $res->original ?? [];
$data = $responseData['data']['data'] ?? [];
if (empty($data)) {
Log::error("No data returned from API", ['url' => $url, 'uuid' => $uuid]);
return false;
}
@@ -335,4 +547,113 @@ class ServiceSIMBG
}
}
public function syncTaskAssignments($uuid){
try{
$init_token = $this->getToken();
$token = $init_token->original['data']['token']['access'];
$url = "/api/pbg/v1/list-tim-penilai/". $uuid . "/?page=1&size=10";
$headers = [
'Authorization' => "Bearer " . $token,
];
$response = $this->service_client->get($url, $headers);
$datas = $response->original['data']['data'] ?? [];
if(empty($datas)){
return false;
}
$task_assignments = [];
foreach ($datas as $data) {
$task_assignments[] = [
'pbg_task_uid' => $uuid,
'user_id' => $data['user_id'],
'name' => $data['name'],
'username' => $data['username'],
'email' => $data['email'],
'phone_number' => $data['phone_number'],
'role' => $data['role'],
'role_name' => $data['role_name'],
'is_active' => $data['is_active'],
'file' => !empty($data['file']) ? json_encode($data['file']) : null,
'expertise' => !empty($data['expertise']) ? json_encode($data['expertise']) : null,
'experience' => !empty($data['experience']) ? json_encode($data['experience']) : null,
'is_verif' => $data['is_verif'],
'uid' => $data['uid'],
'status' => $data['status'],
'status_name' => $data['status_name'],
'note' => $data['note'],
'ta_id' => $data['id'],
'created_at' => now(),
'updated_at' => now(),
];
}
TaskAssignment::upsert(
$task_assignments,
['uid'],
['ta_id','name', 'username', 'email', 'phone_number', 'role', 'role_name', 'is_active', 'file', 'expertise', 'experience', 'is_verif', 'status', 'status_name', 'note', 'updated_at']
);
return true;
}catch(Exception $e){
Log::error("Failed to sync task assignments", ['error' => $e->getMessage()]);
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;
}
$cleaned = str_replace('.','', $value);
// Otherwise, cast to integer
return (int) $cleaned;
}
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

@@ -57,7 +57,7 @@
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
]
},
"extra": {

View File

@@ -123,5 +123,6 @@ return [
'store' => env('APP_MAINTENANCE_STORE', 'database'),
],
'api_url' => env('API_URL', 'http://localhost:8000')
'api_url' => env('API_URL', 'http://localhost:8000'),
'paginate_per_page' => 50
];

View File

@@ -79,6 +79,8 @@ return [
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
PDO::ATTR_TIMEOUT => 40000,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET SESSION wait_timeout=40000; SET SESSION interactive_timeout=40000;"
]) : [],
],

View File

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

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,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

@@ -0,0 +1,48 @@
<?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::create('task_assignments', function (Blueprint $table) {
$table->id(); // Auto-increment primary key
// Foreign key reference to pbg_tasks (uid column)
$table->string('pbg_task_uid');
$table->foreign('pbg_task_uid')->references('uuid')->on('pbg_task')->onDelete('cascade');
$table->unsignedBigInteger('user_id'); // Reference to users table
$table->string('name');
$table->string('username')->unique();
$table->string('email')->unique();
$table->string('phone_number')->nullable();
$table->unsignedInteger('role'); // Assuming role is numeric
$table->string('role_name');
$table->boolean('is_active')->default(true);
$table->json('file')->nullable(); // Store as JSON if 'file' is an array
$table->string('expertise')->nullable();
$table->string('experience')->nullable();
$table->boolean('is_verif')->default(false);
$table->string('uid')->unique();
$table->unsignedTinyInteger('status')->default(0); // Assuming status is a small integer
$table->string('status_name')->nullable();
$table->text('note')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('task_assignments');
}
};

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('task_assignments', function (Blueprint $table) {
$table->json('expertise')->nullable()->change();
$table->json('experience')->nullable()->change();
$table->bigInteger('ta_id')->nullable()->after('id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('task_assignments', function (Blueprint $table) {
$table->text('expertise')->nullable()->change();
$table->text('experience')->nullable()->change();
$table->dropColumn('ta_id');
});
}
};

View File

@@ -71,9 +71,16 @@ class UsersRoleMenuSeeder extends Seeder
[
"name" => "Laporan",
"url" => "/laporan",
"icon" => "mingcute:task-line",
"icon" => "mingcute:report-line",
"parent_id" => null,
"sort_order" => 6,
],
[
"name" => "Neng Bedas",
"url" => "/chat",
"icon" => "mingcute:wechat-line",
"parent_id" => null,
"sort_order" => 7,
]
];
@@ -92,6 +99,7 @@ class UsersRoleMenuSeeder extends Seeder
$dataSettings = Menu::where('name', 'Data Settings')->first();
$data = Menu::where('name', 'Data')->first();
$laporan = Menu::where('name', 'Laporan')->first();
$chat_bedas = Menu::where('name', 'Neng Bedas')->first();
// create children menu
$children_menus = [
@@ -111,7 +119,7 @@ class UsersRoleMenuSeeder extends Seeder
],
[
"name" => "Dashboard Potensi",
"url" => "dashboard.lack_of_potential",
"url" => null,
"icon" => null,
"parent_id" => $dashboard->id,
"sort_order" => 3,
@@ -167,7 +175,7 @@ class UsersRoleMenuSeeder extends Seeder
],
[
"name" => "Reklame",
"url" => "advertisements.index",
"url" => "web.advertisements.index",
"icon" => null,
"parent_id" => $data->id,
"sort_order" => 2,
@@ -181,21 +189,21 @@ class UsersRoleMenuSeeder extends Seeder
],
[
"name" => "UMKM",
"url" => "umkm.index",
"url" => "web-umkm.index",
"icon" => null,
"parent_id" => $data->id,
"sort_order" => 4,
],
[
"name" => "Pariwisata",
"url" => "tourisms.index",
"url" => "web-tourisms.index",
"icon" => null,
"parent_id" => $data->id,
"sort_order" => 5,
],
[
"name" => "Tata Ruang",
"url" => "spatial-plannings.index",
"url" => "web-spatial-plannings.index",
"icon" => null,
"parent_id" => $data->id,
"sort_order" => 6,
@@ -209,11 +217,39 @@ class UsersRoleMenuSeeder extends Seeder
],
[
"name" => "Lap Pariwisata",
"url" => "tourisms.index",
"url" => "tourisms-report.index",
"icon" => null,
"parent_id" => $laporan->id,
"sort_order" => 1,
],
[
"name" => "Lap Pimpinan",
"url" => "bigdata-resumes",
"icon" => null,
"parent_id" => $laporan->id,
"sort_order" => 2,
],
[
"name" => "Chat",
"url" => "main-chatbot.index",
"icon" => null,
"parent_id" => $chat_bedas->id,
"sort_order" => 1,
],
[
"name" => "Dalam Sistem",
"url" => "dashboard.potentials.inside_system",
"icon" => null,
"parent_id" => Menu::where('name', 'Dashboard Potensi')->first()->id,
"sort_order" => 1,
],
[
"name" => "Luar Sistem",
"url" => "dashboard.potentials.outside_system",
"icon" => null,
"parent_id" => Menu::where('name', 'Dashboard Potensi')->first()->id,
"sort_order" => 2,
],
];
foreach ($children_menus as $child_menu) {
@@ -237,6 +273,10 @@ class UsersRoleMenuSeeder extends Seeder
$spatial_plannings = Menu::where('name', 'Tata Ruang')->first();
$pdam = Menu::where('name', 'PDAM')->first();
$peta = Menu::where('name', 'PETA')->first();
$bigdata_resume = Menu::where('name', 'Lap Pimpinan')->first();
$chatbot = Menu::where('name', 'Chat')->first();
$dalam_sistem = Menu::where('name', 'Dalam Sistem')->first();
$luar_sistem = Menu::where('name', 'Luar Sistem')->first();
// Superadmin gets all menus
$superadmin->menus()->sync([
@@ -247,6 +287,7 @@ class UsersRoleMenuSeeder extends Seeder
$dataSettings->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false],
$data->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false],
$laporan->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false],
$chat_bedas->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false],
// children
$dashboard_pimpinan->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$dashboard_pbg->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
@@ -264,7 +305,11 @@ class UsersRoleMenuSeeder extends Seeder
$lack_of_potentials->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$spatial_plannings->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$pdam->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$peta->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
// $peta->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$dalam_sistem->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$luar_sistem->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$bigdata_resume->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$chatbot->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
]);
// Admin gets limited menus
@@ -279,6 +324,7 @@ class UsersRoleMenuSeeder extends Seeder
$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],
]);
// Attach User to role super admin
User::findOrFail(1)->roles()->sync([$superadmin->id]);
}

View File

@@ -0,0 +1,21 @@
CREATE VIEW v_advertisements AS
SELECT
a.no,
a.business_name,
a.npwpd,
a.advertisement_type,
a.advertisement_content,
a.business_address,
a.advertisement_location,
v.village_name AS village_name,
d.district_name AS district_name,
a.length,
a.width,
a.viewing_angle,
a.face,
a.area,
a.angle,
a.contact
FROM advertisements a
JOIN villages v ON a.village_code = v.village_code
JOIN districts d ON a.district_code = d.district_code;

View File

@@ -0,0 +1,8 @@
CREATE VIEW v_tourisms_based_kbli AS
SELECT kbli_title, total_records
FROM (
SELECT kbli, kbli_title, COUNT(*) AS total_records
FROM tourisms
GROUP BY kbli, kbli_title
) AS subquery
ORDER BY total_records DESC;

View File

@@ -0,0 +1,29 @@
CREATE VIEW v_tourisms AS
SELECT
t.project_id,
t.project_type_id,
t.nib,
t.business_name,
t.oss_publication_date,
t.investment_status_description,
t.business_form,
t.project_risk,
t.project_name,
t.business_scale,
t.business_address,
v.village_name as village_name,
d.district_name as district_name,
t.longitude,
t.latitude,
t.project_submission_date,
t.kbli_title,
t.supervisory_sector,
t.user_name,
t.email,
t.contact,
t.land_area_in_m2,
t.investment_amount,
t.tki
FROM tourisms t
JOIN villages v on t.village_code = v.village_code
JOIN districts d on t.district_code = d.district_code;

View File

@@ -0,0 +1,28 @@
CREATE VIEW v_umkms AS
SELECT
u.business_address,
u.business_contact,
u.business_desc,
bf.business_form,
u.business_id_number,
u.business_name,
bs.business_scale,
u.business_type,
u.created_at,
d.district_name,
u.land_area,
u.number_of_employee,
u.owner_address,
u.owner_contact,
u.owner_id,
u.owner_name,
ps.permit_status,
u.revenue,
u.updated_at,
v.village_name
FROM umkms u
JOIN business_form bf on u.business_form_id = bf.id
JOIN permit_status ps on u.permit_status_id = ps.id
JOIn business_scale bs on u.business_scale_id = bs.id
JOIN villages v on u.village_code = v.village_code
JOIN districts d on u.district_code = v.district_code;

34
deploy.sh Executable file
View File

@@ -0,0 +1,34 @@
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_ALLOW_SUPERUSER=1 composer install --no-interaction --optimize-autoloader
echo "🗄️ Running migrations..."
php artisan migrate --force
echo "⚡ Optimizing application..."
php artisan optimize:clear
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!"

5851
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +1,37 @@
{
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite"
},
"devDependencies": {
"autoprefixer": "^10.4.20",
"axios": "^1.7.4",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^1.0",
"postcss": "^8.4.47",
"sass": "^1.81.1",
"tailwindcss": "^3.4.13",
"vite": "^5.0"
},
"dependencies": {
"apexcharts": "^3.44.2",
"big.js": "^6.2.2",
"bootstrap": "^5.3.3",
"countup.js": "^2.3.2",
"dropzone": "^5.9.0",
"flatpickr": "^4.6.13",
"gmaps": "^0.4.25",
"gridjs": "^5.1.0",
"iconify-icon": "^2.1.0",
"jsvectormap": "^1.5.1",
"moment": "^2.29.4",
"node-waves": "^0.7.6",
"quill": "^1.3.7",
"simplebar": "^5.3.9",
"sweetalert2": "^11.16.0",
"wnumb": "^1.2.0"
}
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite"
},
"devDependencies": {
"autoprefixer": "^10.4.20",
"axios": "^1.7.4",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^1.0",
"postcss": "^8.4.47",
"sass": "^1.81.1",
"tailwindcss": "^3.4.13",
"vite": "^5.0"
},
"dependencies": {
"apexcharts": "^3.44.2",
"big.js": "^6.2.2",
"bootstrap": "^5.3.3",
"countup.js": "^2.3.2",
"dropzone": "^5.9.0",
"flatpickr": "^4.6.13",
"gmaps": "^0.4.25",
"gridjs": "^5.1.0",
"iconify-icon": "^2.1.0",
"jsvectormap": "^1.5.1",
"leaflet": "^1.9.4",
"moment": "^2.29.4",
"node-waves": "^0.7.6",
"quill": "^1.3.7",
"simplebar": "^5.3.9",
"sweetalert2": "^11.16.0",
"wnumb": "^1.2.0"
}
}

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,11 +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."
"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.\n\n The query should include `LIMIT 5` to restrict the results."
},
"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."
"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.\n\n The query should include `LIMIT 5` to restrict the results."
},
"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."
"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.\n\n The query should include `LIMIT 5` to restrict the results."
}
}

View File

@@ -0,0 +1,169 @@
{
"reklame": {
"table_name": "v_advertisements",
"list_column": [
"no",
"business_name",
"npwpd",
"advertisement_type",
"advertisement_content",
"business_address",
"advertisement_location",
"village_name",
"district_name",
"length",
"width",
"viewing_angle",
"face",
"area",
"angle",
"contact"
]
},
"business_or_industries": {
"table_name": "business_or_industries",
"list_column": [
"nama_kecamatan",
"nama_kelurahan",
"nop",
"nama_wajib_pajak",
"alamat_wajib_pajak",
"alamat_objek_pajak",
"luas_bumi",
"luas_bangunan",
"njop_bumi",
"njop_bangunan",
"ketetapan",
"tahun_pajak",
"created_at",
"updated_at"
]
},
"customers": {
"table_name": "customers",
"list_column": [
"nomor_pelanggan",
"kota_pelayanan",
"nama",
"alamat",
"latitude",
"longitude",
"created_at",
"updated_at"
]
},
"pbg": {
"table_name": "pbg_task",
"list_column": [
"uuid",
"name",
"owner_name",
"application_type",
"application_type_name",
"condition",
"registration_number",
"document_number",
"address",
"status_name",
"slf_status_name",
"function_type",
"consultation_type",
"due_date",
"land_certificate_phase",
"created_at",
"updated_at",
"task_created_at"
]
},
"retribusi": {
"table_name": "v_pbg_task_with_retributions",
"list_column": [
"uuid",
"name",
"owner_name",
"application_type",
"application_type_name",
"condition",
"registration_number",
"document_number",
"address",
"status_name",
"slf_status_name",
"consultation_type",
"due_date",
"land_certificate_phase",
"created_at",
"updated_at",
"task_created_at",
"nilai_retribusi_bangunan"
]
},
"spatial_plannings": {
"table_name": "spatial_plannings",
"list_column": [
"created_at",
"updated_at",
"name",
"kbli",
"activities",
"area",
"location",
"number",
"date"
]
},
"tourisms": {
"table_name": "v_tourisms",
"list_column": [
"project_id",
"project_type_id",
"nib",
"business_name",
"oss_publication_date",
"investment_status_description",
"business_form",
"project_risk",
"project_name",
"business_scale",
"business_address",
"village_name",
"district_name",
"longitude",
"latitude",
"project_submission_date",
"kbli_title",
"supervisory_sector",
"user_name",
"email",
"contact",
"land_area_in_m2",
"investment_amount",
"tki"
]
},
"umkms": {
"table_name": "v_umkms",
"list_column": [
"business_address",
"business_contact",
"business_desc",
"business_form",
"business_id_number",
"business_name",
"business_scale",
"business_type",
"created_at",
"district_name",
"land_area",
"number_of_employee",
"owner_address",
"owner_contact",
"owner_id",
"owner_name",
"permit_status",
"revenue",
"updated_at",
"village_name"
]
}
}

View File

@@ -0,0 +1,178 @@
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 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.initEvents();
}
async initEvents() {
await this.initBigdataResumeTable();
// this.handleSearch();
}
async initBigdataResumeTable() {
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;",
},
},
],
pagination: {
limit: 50,
server: {
url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${
page + 1
}`,
},
},
sort: true,
search: {
server: {
url: (prev, keyword) => `${prev}?search=${keyword}`,
},
debounceTimeout: 1000,
},
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,
},
width: "auto",
fixedHeader: true,
});
return new Promise((resolve) => {
this.table.render(tableContainer);
this.table.on("ready", resolve); // Tunggu event "ready"
});
}
handleSearch() {
document.getElementById("search-btn").addEventListener("click", () => {
let searchValue = document.getElementById("search-box").value;
if (!this.table) {
// Ensure table is initialized
console.error("Table element not found!");
return;
}
this.table
.updateConfig({
server: {
url: `${GlobalConfig.apiHost}/api/bigdata-report?search=${searchValue}`,
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,
},
})
.forceRender();
});
}
}
document.addEventListener("DOMContentLoaded", function (e) {
new BigdataResume();
});

View File

@@ -5,9 +5,9 @@ Dropzone.autoDiscover = false;
var previewTemplate,
dropzone,
dropzonePreviewNode = document.querySelector("#dropzone-preview-list");
console.log(previewTemplate);
console.log(dropzone);
console.log(dropzonePreviewNode);
const uploadButton = document.getElementById("btnUploadBusinessIndustry");
const spinner = document.getElementById("spinner");
const toastNotification = document.getElementById("toastNotification");
const toast = new bootstrap.Toast(toastNotification);
@@ -29,97 +29,50 @@ const toast = new bootstrap.Toast(toastNotification);
.getAttribute("content")}`,
},
init: function () {
// Listen for the success event
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 =
response.message;
toast.show();
document.getElementById("submit-upload").innerHTML =
"Upload Files";
// Tunggu sebentar lalu reload halaman
setTimeout(() => {
window.location.href = "/data/business-industries";
}, 2000);
});
// Listen for the error event
this.on("error", function (file, errorMessage) {
console.error("Error uploading file:", file);
console.error("Error message:", errorMessage);
// Handle the error response
// Show error toast
document.getElementById("toast-message").innerText =
errorMessage.message;
toast.show();
document.getElementById("submit-upload").innerHTML =
"Upload Files";
uploadButton.disabled = false;
spinner.classList.add("d-none");
});
},
})));
// Add event listener to control the submission manually
document.querySelector("#submit-upload").addEventListener("click", function () {
console.log("Ini adalah value dropzone", dropzone.files[0]);
const formData = new FormData();
console.log("Dropzonefiles", dropzone.files);
document
.querySelector("#btnUploadBusinessIndustry")
.addEventListener("click", function () {
console.log("Ini adalah value dropzone", dropzone.files[0]);
const formData = new FormData();
this.innerHTML =
'<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>Loading...';
if (dropzone.files.length > 0) {
formData.append("file", dropzone.files[0]);
dropzone.processQueue(); // Ini akan manual memicu upload
uploadButton.disabled = true;
spinner.classList.remove("d-none");
} else {
document.getElementById("toast-message").innerText =
"Please add a file first.";
toast.show();
uploadButton.disabled = false;
spinner.classList.add("d-none");
}
});
// Pastikan ada file dalam queue sebelum memprosesnya
if (dropzone.files.length > 0) {
formData.append("file", dropzone.files[0]);
console.log("ini adalah form data on submit", ...formData);
dropzone.processQueue(); // Ini akan manual memicu upload
} else {
// Show error toast when no file is selected
document.getElementById("toast-message").innerText =
"Please add a file first.";
toast.show();
document.getElementById("submit-upload").innerHTML = "Upload Files";
}
});
// Optional: Listen for the 'addedfile' event to log or control file add behavior
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("addedfile", function (file) {});
dropzone.on("complete", function (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: {
limit: 15,
limit: 50,
server: {
url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${
@@ -77,6 +77,7 @@ class BusinessIndustries {
server: {
url: (prev, keyword) => `${prev}?search=${keyword}`,
},
debounceTimeout: 1000,
},
server: {
url: `${GlobalConfig.apiHost}/api/api-business-industries`,

View File

@@ -0,0 +1,155 @@
import GlobalConfig from "../global-config.js";
document.addEventListener("DOMContentLoaded", function () {
const timeElements = document.querySelectorAll(".sending-message-time p");
timeElements.forEach((element) => {
element.textContent = getCurrentTime();
});
const textarea = document.getElementById("user-message");
const sendButton = document.getElementById("send");
const conversationArea = document.querySelector(".row.flex-grow");
const chatHistory = [];
// Fungsi untuk mengirim pesan
async function sendMessage() {
const userText = textarea.value.trim();
if (userText !== "") {
// Kosongkan textarea setelah mengirim
textarea.value = "";
// Tambahkan pesan user ke UI
addMessage(userText, "user");
// Tambahkan pesan bot sementara dengan "Loading..."
const botMessageElement = addMessage('<div class="bot-message-text">...</div>', "bot");
const messageTextContainer = botMessageElement.querySelector(".bot-message-text");
if (messageTextContainer) {
messageTextContainer.innerHTML = '<div class="loader ms-3"></div>';
}
// Panggil API untuk mendapatkan respons dari bot
const botResponse = await getBotResponse(userText, chatHistory);
// Perbarui pesan bot dengan respons yang sebenarnya
if (messageTextContainer) {
messageTextContainer.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 getCurrentTime() {
const now = new Date();
return now.getHours().toString().padStart(2, "0") + ":" + now.getMinutes().toString().padStart(2, "0");
}
function addMessage(text, sender) {
const messageRow = document.createElement("div");
// Atur posisi berdasarkan sender (user -> end, bot -> start)
messageRow.classList.add("row", "flex-grow", "overflow-auto", sender === "user" ? "justify-content-end" : "justify-content-start");
const messageCol = document.createElement("div");
messageCol.classList.add("col-9", "w-auto");
// Atur lebar maksimum berdasarkan sender
messageCol.style.maxWidth = sender === "user" ? "50%" : "75%";
// Container untuk menyimpan nama dan bubble chat
const messageWrapper = document.createElement("div");
messageWrapper.classList.add("d-flex", "flex-column");
// Tambahkan Nama di luar bubble chat
const messageName = document.createElement("p");
messageName.classList.add("fw-bolder", sender === "user" ? "text-end" : "text-start", "mb-1");
messageName.textContent = sender === "user" ? "You" : "Neng Bedas";
// Bubble Chat
const messageContainer = document.createElement("div");
messageContainer.classList.add("p-2", "rounded", "mb-2", "d-inline-block");
if (sender === "user") {
messageContainer.classList.add("user-response", "bg-primary", "text-white");
} else {
messageContainer.classList.add("bot-response", "bg-light");
}
const messageContent = document.createElement("div");
messageContent.classList.add("bot-message-text", "mb-0", "text-start");
messageContent.textContent = text;
// Waktu di dalam bubble chat
const messageTime = document.createElement("div");
messageTime.classList.add("sending-message-time", "text-end", "mt-1");
messageTime.innerHTML = `<p class="small mb-0 ${sender === "user" ? "text-white text-start" : "text-muted"}">${getCurrentTime()}</p>`;
messageContainer.appendChild(messageContent);
messageContainer.appendChild(messageTime);
// Jika pengirim adalah bot, tambahkan avatar
if (sender !== "user") {
const avatarContainer = document.createElement("div");
avatarContainer.classList.add("col-auto", "pe-0");
const avatarImg = document.createElement("img");
avatarImg.classList.add("rounded-circle");
avatarImg.width = 45;
avatarImg.src = "/images/iconchatbot.jpeg";
avatarImg.alt = "bot-avatar";
avatarContainer.appendChild(avatarImg);
messageRow.appendChild(avatarContainer);
}
// Masukkan nama dan bubble ke dalam wrapper
messageWrapper.appendChild(messageName);
messageWrapper.appendChild(messageContainer);
messageCol.appendChild(messageWrapper);
messageRow.appendChild(messageCol);
conversationArea.appendChild(messageRow);
conversationArea.scrollTop = conversationArea.scrollHeight;
return messageContainer;
}
// Fungsi untuk memanggil API
async function getBotResponse(userText, historyChat) {
try {
const url = `${GlobalConfig.apiHost}/api/main-generate-text`;
const response = await fetch(url, {
method: "POST",
body: JSON.stringify({prompt: userText, chatHistory: historyChat}),
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
},
});
const data = await response.json();
const rawBotResponse = data.nlpResponse;
// Tambahkan ke chatHistory
chatHistory.push({
user: userText,
rawBotResponse: rawBotResponse,
});
return data.response || "Maaf, saya tidak mengerti.";
} catch (error) {
console.error("Error fetching bot response:", error);
return "Terjadi kesalahan, coba lagi nanti.";
}
}
});

View File

@@ -1,11 +1,63 @@
import GlobalConfig from "../global-config.js";
document.addEventListener("DOMContentLoaded", function () {
const tabs = document.querySelectorAll(".nav-link");
const timeElements = document.querySelectorAll(".sending-message-time p");
timeElements.forEach((element) => {
element.textContent = getCurrentTime();
});
function activateTab(tab) {
tabs.forEach(btn => {
btn.classList.remove("border-3", "bg-primary", "text-white"); // Reset semua tab
});
tab.classList.add("border-3", "bg-primary", "text-white"); // Tambahkan warna pada tab aktif
}
tabs.forEach(tab => {
tab.addEventListener("click", function () {
activateTab(this);
});
});
// Set warna awal untuk tab aktif (jika ada)
const initialActiveTab = document.querySelector(".nav-link.active");
if (initialActiveTab) {
activateTab(initialActiveTab);
}
document.querySelectorAll(".nav-link").forEach(tab => {
tab.addEventListener("click", function () {
setTimeout(() => {
const tab_active = getActiveTabId();
console.log("Active Tab ID:", tab_active);
// Hapus semua chat kecuali pesan default bot
conversationArea.innerHTML = `
<div class="row flex-grow overflow-auto align-items-start">
<!-- Avatar -->
<div class="col-auto alignpe-0">
<img class="rounded-circle" width="45" src="/images/iconchatbot.jpeg" alt="avatar-3">
</div>
<!-- Nama dan Bubble Chat -->
<div class="col-9 w-auto">
<!-- Nama Bot -->
<p class="fw-bolder mb-1">Neng Bedas</p>
<!-- Bubble Chat -->
<div class="bot-response p-2 bg-light rounded mb-2 d-inline-block">
<p class="mb-0">Halo! Ada yang bisa saya bantu?</p>
<!-- Waktu (Tetap di Dalam Bubble Chat) -->
<div class="sending-message-time text-end mt-1">
<p class="text-muted small mb-0">Now</p>
</div>
</div>
</div>
</div>
`;
}, 100); // Timeout untuk memastikan class `active` sudah diperbarui
});
});
@@ -13,6 +65,7 @@ document.addEventListener("DOMContentLoaded", function () {
const textarea = document.getElementById("user-message");
const sendButton = document.getElementById("send");
const conversationArea = document.querySelector(".row.flex-grow");
const chatHistory = [];
// Fungsi untuk mengirim pesan
async function sendMessage() {
@@ -28,13 +81,20 @@ document.addEventListener("DOMContentLoaded", function () {
addMessage(userText, "user");
// Tambahkan pesan bot sementara dengan "Loading..."
const botMessageElement = addMessage('<div class="loader w-auto"></div>', "bot");
const botMessageElement = addMessage('<div class="bot-message-text">...</div>', "bot");
// Panggil API untuk mendapatkan response dari bot
const botResponse = await getBotResponse(currentTab, userText);
const messageTextContainer = botMessageElement.querySelector(".bot-message-text");
if (messageTextContainer) {
messageTextContainer.innerHTML = '<div class="loader ms-3"></div>';
}
// Panggil API untuk mendapatkan respons dari bot
const botResponse = await getBotResponse(currentTab, userText, chatHistory);
// Perbarui pesan bot dengan respons yang sebenarnya
botMessageElement.innerHTML = botResponse;;
if (messageTextContainer) {
messageTextContainer.innerHTML = botResponse;
}
}
}
@@ -49,110 +109,86 @@ document.addEventListener("DOMContentLoaded", function () {
}
});
function getCurrentTime() {
const now = new Date();
return now.getHours().toString().padStart(2, "0") + ":" + now.getMinutes().toString().padStart(2, "0");
}
function addMessage(text, sender) {
const messageRow = document.createElement("div");
messageRow.classList.add("row", "flex-grow", "overflow-auto");
// Atur posisi berdasarkan sender (user -> end, bot -> start)
messageRow.classList.add("row", "flex-grow", "overflow-auto", sender === "user" ? "justify-content-end" : "justify-content-start");
const messageCol = document.createElement("div");
messageCol.classList.add("w-auto", "d-inline-block"); // Menyesuaikan lebar konten
messageCol.classList.add("col-9", "w-auto");
// Atur lebar maksimum berdasarkan sender
messageCol.style.maxWidth = sender === "user" ? "50%" : "75%";
// Container untuk menyimpan nama dan bubble chat
const messageWrapper = document.createElement("div");
messageWrapper.classList.add("d-flex", "flex-column");
// Tambahkan Nama di luar bubble chat
const messageName = document.createElement("p");
messageName.classList.add("fw-bolder", sender === "user" ? "text-end" : "text-start", "mb-1");
messageName.textContent = sender === "user" ? "You" : "Neng Bedas";
// Bubble Chat
const messageContainer = document.createElement("div");
messageContainer.classList.add("p-2", "rounded", "mb-2", "d-inline-block");
if (sender === "user") {
messageCol.classList.add("ms-auto", "max-w-50"); // Rata kanan, max 50% (setara col-6)
messageContainer.classList.add("user-response", "bg-primary", "text-white");
} else {
messageCol.classList.add("max-w-75"); // Max 75% (setara col-9)
messageContainer.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 messageContent = document.createElement("div");
messageContent.classList.add("bot-message-text", "mb-0", "text-start");
messageContent.textContent = text;
// Waktu di dalam bubble chat
const messageTime = document.createElement("div");
messageTime.classList.add("sending-message-time", "text-end", "mt-1");
messageTime.innerHTML = `<p class="small mb-0 ${sender === "user" ? "text-white text-start" : "text-muted"}">${getCurrentTime()}</p>`;
messageContainer.appendChild(messageContent);
messageContainer.appendChild(messageTime);
// Jika pengirim adalah bot, tambahkan avatar
if (sender !== "user") {
const avatarContainer = document.createElement("div");
avatarContainer.classList.add("col-auto", "pe-0");
const avatarImg = document.createElement("img");
avatarImg.classList.add("rounded-circle");
avatarImg.width = 32;
avatarImg.width = 45;
avatarImg.src = "/images/iconchatbot.jpeg";
avatarImg.alt = "bot-avatar";
avatarSpan.appendChild(avatarImg);
messageCol.appendChild(avatarSpan);
avatarContainer.appendChild(avatarImg);
messageRow.appendChild(avatarContainer);
}
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);
// Masukkan nama dan bubble ke dalam wrapper
messageWrapper.appendChild(messageName);
messageWrapper.appendChild(messageContainer);
messageCol.appendChild(messageWrapper);
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
// }
return messageContainer;
}
// Fungsi untuk memanggil API
async function getBotResponse(tab_active, userText) {
async function getBotResponse(tab_active, userText, historyChat) {
try {
const url = `${GlobalConfig.apiHost}/api/generate-text`;
const response = await fetch(url, {
method: "POST",
body: JSON.stringify({tab_active:tab_active, prompt: userText }),
body: JSON.stringify({tab_active:tab_active, prompt: userText, chatHistory: historyChat }),
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
@@ -162,6 +198,12 @@ document.addEventListener("DOMContentLoaded", function () {
});
const data = await response.json();
const rawBotResponse = data.nlpResponse;
// Tambahkan ke chatHistory
chatHistory.push({
user: userText,
rawBotResponse: rawBotResponse,
});
return data.response || "Maaf, saya tidak mengerti.";
} catch (error) {
console.error("Error fetching bot response:", error);

View File

@@ -53,7 +53,7 @@ class Customers {
},
],
pagination: {
limit: 15,
limit: 50,
server: {
url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${
@@ -66,6 +66,7 @@ class Customers {
server: {
url: (prev, keyword) => `${prev}?search=${keyword}`,
},
debounceTimeout: 1000,
},
server: {
url: `${GlobalConfig.apiHost}/api/customers`,
@@ -91,6 +92,8 @@ class Customers {
}).render(tableContainer);
}
handleSearch() {}
async handleDelete(deleteButton) {
const id = deleteButton.getAttribute("data-id");

View File

@@ -23,121 +23,9 @@ class BigData {
}
async updateData(filterDate) {
try {
console.log("Filtering data for date:", 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.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.initChartTargetPAD(filterDate);
this.initChartUsaha();
this.initChartNonUsaha();
this.initChartTotalPotensi();
@@ -180,190 +68,17 @@ class BigData {
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) {
console.error("Network response was not ok", response);
}
const data = await response.json();
return {
countData: data.data.count,
totalData: data.data.total,
};
} 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() {
initChartTargetPAD(filterDate) {
const year =
filterDate === "latest"
? new Date().getFullYear()
: new Date(filterDate).getFullYear();
document
.querySelectorAll(".document-title.chart-target-pad")
.forEach((element) => {
element.innerText = `Target PAD ${year}`;
});
document
.querySelectorAll(".document-count.chart-target-pad")
.forEach((element) => {
@@ -373,7 +88,6 @@ class BigData {
.querySelectorAll(".document-total.chart-target-pad")
.forEach((element) => {
element.innerText = `Rp.${addThousandSeparators(
// this.bigTargetPAD.toString()
this.resumeBigData.target_pad.sum.toString()
)}`;
});
@@ -589,20 +303,18 @@ class BigData {
document
.querySelectorAll(".document-count.chart-potensi-tata-ruang")
.forEach((element) => {
element.innerText = "";
element.innerText = `${this.resumeBigData.tata_ruang.count}`;
});
document
.querySelectorAll(".document-total.chart-potensi-tata-ruang")
.forEach((element) => {
element.innerText = `Rp.${addThousandSeparators(
// this.bigTotalTataRuang.toString()
this.resumeBigData.tata_ruang.sum.toString()
)}`;
});
document
.querySelectorAll(".small-percentage.chart-potensi-tata-ruang")
.forEach((element) => {
// element.innerText = `${this.percentageResultTataRuang}%`;
element.innerText = `${this.resumeBigData.tata_ruang.percentage}%`;
});
}

View File

@@ -16,6 +16,19 @@ class LackOfPotential {
this.pdamCount = this.allCountData.total_pdam ?? 0;
this.tataRuangCount = this.allCountData.total_tata_ruang ?? 0;
let dataReportTourism = this.allCountData.data_report;
this.totalVilla = dataReportTourism
.filter((item) => item.kbli_title.toLowerCase() === "vila")
.reduce((sum, item) => sum + item.total_records, 0);
this.totalRestoran = dataReportTourism
.filter((item) => item.kbli_title.toLowerCase() === "restoran")
.reduce((sum, item) => sum + item.total_records, 0);
this.totalPariwisata = dataReportTourism.reduce(
(sum, item) => sum + item.total_records,
0
);
this.bigTargetPAD = new Big(this.totalTargetPAD ?? 0);
this.bigTotalPotensi = new Big(this.totalPotensi.total ?? 0);
this.bigTotalLackPotential = this.bigTargetPAD.minus(
@@ -140,6 +153,15 @@ class LackOfPotential {
document.getElementById("pdam-count").innerText = this.pdamCount;
document.getElementById("pbb-bangunan-count").innerText =
this.tataRuangCount;
document.getElementById("tata-ruang-count").innerText =
this.tataRuangCount;
document.getElementById("tata-ruang-usaha-count").innerText =
this.tataRuangCount;
document.getElementById("restoran-count").innerText =
this.totalRestoran;
document.getElementById("villa-count").innerText = this.totalVilla;
document.getElementById("pariwisata-count").innerText =
this.totalPariwisata;
}
}
document.addEventListener("DOMContentLoaded", async function (e) {

View File

@@ -0,0 +1,194 @@
import Big from "big.js";
import GlobalConfig, { addThousandSeparators } from "../../global-config.js";
import InitDatePicker from "../../utils/InitDatePicker.js";
class DashboardPotentialInsideSystem {
async init() {
new InitDatePicker(
"#datepicker-lack-of-potential",
this.handleChangedDate.bind(this)
).init();
this.bigTotalLackPotential = 0;
this.totalPotensi = await this.getDataTotalPotensi("latest");
this.totalTargetPAD = await this.getDataSettings("TARGET_PAD");
this.allCountData = await this.getValueDashboard();
this.reklameCount = this.allCountData.total_reklame ?? 0;
this.pdamCount = this.allCountData.total_pdam ?? 0;
this.tataRuangCount = this.allCountData.total_tata_ruang ?? 0;
let dataReportTourism = this.allCountData.data_report;
this.totalVilla = dataReportTourism
.filter((item) => item.kbli_title.toLowerCase() === "vila")
.reduce((sum, item) => sum + item.total_records, 0);
this.totalRestoran = dataReportTourism
.filter((item) => item.kbli_title.toLowerCase() === "restoran")
.reduce((sum, item) => sum + item.total_records, 0);
this.totalPariwisata = dataReportTourism.reduce(
(sum, item) => sum + item.total_records,
0
);
this.bigTargetPAD = new Big(this.totalTargetPAD ?? 0);
this.bigTotalPotensi = new Big(this.totalPotensi.total ?? 0);
this.bigTotalLackPotential = this.bigTargetPAD.minus(
this.bigTotalPotensi
);
this.initChartKekuranganPotensi();
this.initDataValueDashboard();
}
async handleChangedDate(filterDate) {
const totalPotensi = await this.getDataTotalPotensi(filterDate);
this.bigTotalPotensi = new Big(totalPotensi.total ?? 0);
this.bigTotalLackPotential = this.bigTargetPAD.minus(
this.bigTotalPotensi
);
this.initChartKekuranganPotensi();
}
async getDataTotalPotensi(filterDate) {
try {
const response = await fetch(
`${GlobalConfig.apiHost}/api/bigdata-resume?filterByDate=${filterDate}`,
{
credentials: "include",
headers: {
Authorization: `Bearer ${
document.querySelector("meta[name='api-token']")
.content
}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
console.error("Network response was not ok", response);
}
const data = await response.json();
return {
total: data.total_potensi.sum,
};
} catch (error) {
console.error("Error fetching chart data:", error);
return null;
}
}
async getDataSettings(string_key) {
try {
const response = await fetch(
`${GlobalConfig.apiHost}/api/data-settings?search=${string_key}`,
{
credentials: "include",
headers: {
Authorization: `Bearer ${
document.querySelector("meta[name='api-token']")
.content
}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
console.error("Network response was not ok", response);
}
const data = await response.json();
return data.data[0].value;
} catch (error) {
console.error("Error fetching chart data:", error);
return 0;
}
}
async getValueDashboard() {
try {
const response = await fetch(
`${GlobalConfig.apiHost}/api/dashboard-potential-count`,
{
credentials: "include",
headers: {
Authorization: `Bearer ${
document.querySelector("meta[name='api-token']")
.content
}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
console.error("Network response was not ok", response);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching chart data:", error);
return 0;
}
}
initChartKekuranganPotensi() {
document
.querySelectorAll(".document-count.chart-lack-of-potential")
.forEach((element) => {
element.innerText = ``;
});
document
.querySelectorAll(".document-total.chart-lack-of-potential")
.forEach((element) => {
element.innerText = `Rp.${addThousandSeparators(
this.bigTotalLackPotential.toString()
)}`;
});
document
.querySelectorAll(".small-percentage.chart-lack-of-potential")
.forEach((element) => {
element.innerText = ``;
});
}
initDataValueDashboard() {
document.getElementById("reklame-count").innerText = this.reklameCount;
document.getElementById("pdam-count").innerText = this.pdamCount;
document.getElementById("pbb-bangunan-count").innerText =
this.tataRuangCount;
document.getElementById("tata-ruang-count").innerText =
this.tataRuangCount;
document.getElementById("tata-ruang-usaha-count").innerText =
this.tataRuangCount;
document.getElementById("restoran-count").innerText =
this.totalRestoran;
document.getElementById("villa-count").innerText = this.totalVilla;
document.getElementById("pariwisata-count").innerText =
this.totalPariwisata;
}
}
document.addEventListener("DOMContentLoaded", async function (e) {
await new DashboardPotentialInsideSystem().init();
});
function resizeDashboard() {
let targetElement = document.getElementById("lack-of-potential-wrapper");
let dashboardElement = document.getElementById(
"lack-of-potential-fixed-container"
);
let targetWidth = targetElement.offsetWidth;
let dashboardWidth = 1400;
let scaleFactor = (targetWidth / dashboardWidth).toFixed(2);
// Prevent scaling beyond 1 (100%) to avoid overflow
scaleFactor = Math.min(scaleFactor, 1);
dashboardElement.style.transformOrigin = "left top";
dashboardElement.style.transition = "transform 0.2s ease-in-out";
dashboardElement.style.transform = `scale(${scaleFactor})`;
// Ensure horizontal scrolling is allowed if necessary
document.body.style.overflowX = "auto";
}
window.addEventListener("load", resizeDashboard);
window.addEventListener("resize", resizeDashboard);

View File

@@ -0,0 +1,121 @@
import InitDatePicker from "../../utils/InitDatePicker.js";
import GlobalConfig, { addThousandSeparators } from "../../global-config.js";
class DashboardPotentialOutsideSystem {
async init() {
new InitDatePicker(
"#datepicker-outside-system",
this.handleChangedDate.bind(this)
).init();
this.bigTotalLackPotential = 0;
this.dataResume = await this.getBigDataResume("latest");
console.log(this.dataResume);
this.initChartNonBusiness();
this.initChartBusiness();
}
async handleChangedDate(filterDate) {
this.dataResume = await this.getBigDataResume(filterDate);
this.initChartNonBusiness();
this.initChartBusiness();
}
async getBigDataResume(filterDate) {
try {
const response = await fetch(
`${GlobalConfig.apiHost}/api/bigdata-resume?filterByDate=${filterDate}`,
{
credentials: "include",
headers: {
Authorization: `Bearer ${
document.querySelector("meta[name='api-token']")
.content
}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
console.error("Network response was not ok", response);
}
return await response.json();
} catch (error) {
console.error("Error fetching chart data:", error);
return null;
}
}
initChartNonBusiness() {
const nonBusinessDoc = this.dataResume?.non_business_document ?? {};
document
.querySelectorAll(".document-count.outside-system-non-business")
.forEach((element) => {
element.innerText = `${nonBusinessDoc.count ?? 0}`;
});
document
.querySelectorAll(".document-total.outside-system-non-business")
.forEach((element) => {
element.innerText = `Rp.${addThousandSeparators(
(nonBusinessDoc.sum ?? 0).toString()
)}`;
});
document
.querySelectorAll(".small-percentage.outside-system-non-business")
.forEach((element) => {
element.innerText = `${nonBusinessDoc.percentage ?? 0}%`;
});
}
initChartBusiness() {
const businessDoc = this.dataResume?.business_document ?? {};
document
.querySelectorAll(".document-count.outside-system-business")
.forEach((element) => {
element.innerText = `${businessDoc.count ?? 0}`;
});
document
.querySelectorAll(".document-total.outside-system-business")
.forEach((element) => {
element.innerText = `Rp.${addThousandSeparators(
(businessDoc.sum ?? 0).toString()
)}`;
});
document
.querySelectorAll(".small-percentage.outside-system-business")
.forEach((element) => {
element.innerText = `${businessDoc.percentage ?? 0}%`;
});
}
}
document.addEventListener("DOMContentLoaded", async function (e) {
await new DashboardPotentialOutsideSystem().init();
});
function resizeDashboard() {
let targetElement = document.getElementById("outside-system-wrapper");
let dashboardElement = document.getElementById(
"outside-system-fixed-container"
);
let targetWidth = targetElement.offsetWidth;
let dashboardWidth = 1400;
let scaleFactor = (targetWidth / dashboardWidth).toFixed(2);
// Prevent scaling beyond 1 (100%) to avoid overflow
scaleFactor = Math.min(scaleFactor, 1);
dashboardElement.style.transformOrigin = "left top";
dashboardElement.style.transition = "transform 0.2s ease-in-out";
dashboardElement.style.transform = `scale(${scaleFactor})`;
// Ensure horizontal scrolling is allowed if necessary
document.body.style.overflowX = "auto";
}
window.addEventListener("load", resizeDashboard);
window.addEventListener("resize", resizeDashboard);

View File

@@ -67,6 +67,7 @@ class DataSettings {
server: {
url: (prev, keyword) => `${prev}?search=${keyword}`,
},
debounceTimeout: 1000,
},
server: {
url: `${GlobalConfig.apiHost}/api/data-settings`,

View File

@@ -3,7 +3,7 @@ import GlobalConfig from "../../global-config";
document.addEventListener("DOMContentLoaded", function () {
const saveButton = document.querySelector(".modal-footer .btn-primary");
const modalButton = document.querySelector(".btn-modal");
const form = document.querySelector("form#create-update-form");
const form = document.querySelector("form#create-update-form");
var authLogo = document.querySelector(".auth-logo");
if (!saveButton || !form) return;
@@ -16,10 +16,10 @@ document.addEventListener("DOMContentLoaded", function () {
Loading...
`;
const isEdit = saveButton.classList.contains("btn-edit");
const formData = new FormData(form)
const toast = document.getElementById('toastEditUpdate');
const toastBody = toast.querySelector('.toast-body');
const toastHeader = toast.querySelector('.toast-header small');
const formData = new FormData(form);
const toast = document.getElementById("toastEditUpdate");
const toastBody = toast.querySelector(".toast-body");
const toastHeader = toast.querySelector(".toast-header small");
const data = {};
@@ -27,9 +27,9 @@ document.addEventListener("DOMContentLoaded", function () {
formData.forEach((value, key) => {
data[key] = value;
});
const url = form.getAttribute("action");
const method = isEdit ? "PUT" : "POST";
fetch(url, {
@@ -40,99 +40,103 @@ document.addEventListener("DOMContentLoaded", function () {
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
}
},
})
.then(response => response.json())
.then(data => {
if (!data.errors) {
// Remove existing icon (if any) before adding the new one
if (authLogo) {
// Hapus ikon yang sudah ada jika ada
const existingIcon = authLogo.querySelector('.bx');
if (existingIcon) {
authLogo.removeChild(existingIcon);
}
// Buat ikon baru
const icon = document.createElement('i');
icon.classList.add('bx', 'bxs-check-square');
icon.style.fontSize = '25px';
icon.style.color = 'green'; // Pastikan 'green' dalam bentuk string
// Tambahkan ikon ke dalam auth-logo
authLogo.appendChild(icon);
}
// Set success message for the toast
toastBody.textContent = isEdit ? "Data updated successfully!" : "Data created successfully!";
toast.classList.add('show'); // Show the toast
setTimeout(() => {
toast.classList.remove('show'); // Hide the toast after 3 seconds
}, 2000);
setTimeout(() => {
window.location.href = '/data/advertisements';
}, 1000);
} else {
if (authLogo) {
// Hapus ikon yang sudah ada jika ada
const existingIcon = authLogo.querySelector('.bx');
if (existingIcon) {
authLogo.removeChild(existingIcon);
}
// Buat ikon baru
const icon = document.createElement('i');
icon.classList.add('bx', 'bxs-error-alt');
icon.style.fontSize = '25px';
icon.style.color = 'red'; // Pastikan 'green' dalam bentuk string
// Tambahkan ikon ke dalam auth-logo
authLogo.appendChild(icon);
}
.then((response) => response.json())
.then((data) => {
if (!data.errors) {
// Remove existing icon (if any) before adding the new one
if (authLogo) {
// Hapus ikon yang sudah ada jika ada
const existingIcon = authLogo.querySelector(".bx");
if (existingIcon) {
authLogo.removeChild(existingIcon);
}
// Buat ikon baru
const icon = document.createElement("i");
icon.classList.add("bx", "bxs-check-square");
icon.style.fontSize = "25px";
icon.style.color = "green"; // Pastikan 'green' dalam bentuk string
// Tambahkan ikon ke dalam auth-logo
authLogo.appendChild(icon);
}
// Set success message for the toast
toastBody.textContent = isEdit
? "Data updated successfully!"
: "Data created successfully!";
toast.classList.add("show"); // Show the toast
setTimeout(() => {
toast.classList.remove("show"); // Hide the toast after 3 seconds
}, 2000);
setTimeout(() => {
window.location.href = "/data/web-advertisements";
}, 1000);
} else {
if (authLogo) {
// Hapus ikon yang sudah ada jika ada
const existingIcon = authLogo.querySelector(".bx");
if (existingIcon) {
authLogo.removeChild(existingIcon);
}
// Buat ikon baru
const icon = document.createElement("i");
icon.classList.add("bx", "bxs-error-alt");
icon.style.fontSize = "25px";
icon.style.color = "red"; // Pastikan 'green' dalam bentuk string
// Tambahkan ikon ke dalam auth-logo
authLogo.appendChild(icon);
}
// Enable button and reset its text on error
modalButton.disabled = false;
modalButton.innerHTML = isEdit ? "Update" : "Create";
// Set error message for the toast
toastBody.textContent =
"Failed: " + (data.message || "Something went wrong");
toast.classList.add("show"); // Show the toast
setTimeout(() => {
toast.classList.remove("show"); // Hide the toast after 3 seconds
}, 3000);
}
})
.catch((errors) => {
if (authLogo) {
// Hapus ikon yang sudah ada jika ada
const existingIcon = authLogo.querySelector(".bx");
if (existingIcon) {
authLogo.removeChild(existingIcon);
}
// Buat ikon baru
const icon = document.createElement("i");
icon.classList.add("bx", "bxs-error-alt");
icon.style.fontSize = "25px";
icon.style.color = "red"; // Pastikan 'green' dalam bentuk string
// Tambahkan ikon ke dalam auth-logo
authLogo.appendChild(icon);
}
// Enable button and reset its text on error
modalButton.disabled = false;
modalButton.innerHTML = isEdit ? "Update" : "Create";
modalButton.innerHTML = isEdit ? "Update" : "Create";
// Set error message for the toast
toastBody.textContent = "Failed: " + (data.message || "Something went wrong");
toast.classList.add('show'); // Show the toast
toastBody.textContent =
"An error occurred while processing your request.";
toast.classList.add("show"); // Show the toast
setTimeout(() => {
toast.classList.remove('show'); // Hide the toast after 3 seconds
toast.classList.remove("show"); // Hide the toast after 3 seconds
}, 3000);
}
})
.catch(errors => {
if (authLogo) {
// Hapus ikon yang sudah ada jika ada
const existingIcon = authLogo.querySelector('.bx');
if (existingIcon) {
authLogo.removeChild(existingIcon);
}
// Buat ikon baru
const icon = document.createElement('i');
icon.classList.add('bx', 'bxs-error-alt');
icon.style.fontSize = '25px';
icon.style.color = 'red'; // Pastikan 'green' dalam bentuk string
// Tambahkan ikon ke dalam auth-logo
authLogo.appendChild(icon);
}
// Enable button and reset its text on error
modalButton.disabled = false;
modalButton.innerHTML = isEdit ? "Update" : "Create";
// Set error message for the toast
toastBody.textContent = "An error occurred while processing your request.";
toast.classList.add('show'); // Show the toast
setTimeout(() => {
toast.classList.remove('show'); // Hide the toast after 3 seconds
}, 3000);
});
});
});
// Fungsi fetchOptions untuk autocomplete server-side
@@ -140,39 +144,43 @@ document.addEventListener("DOMContentLoaded", function () {
let inputValue = document.getElementById(field).value;
if (inputValue.length < 2) return;
let districtValue = document.getElementById("district_name").value; // Ambil kecamatan terpilih
let url = `${GlobalConfig.apiHost}/api/combobox/search-options?query=${encodeURIComponent(inputValue)}&field=${field}`;
let url = `${
GlobalConfig.apiHost
}/api/combobox/search-options?query=${encodeURIComponent(
inputValue
)}&field=${field}`;
// Jika field desa, tambahkan kecamatan sebagai filter
if (field === "village_name") {
url += `&district=${encodeURIComponent(districtValue)}`;
}
fetch(url, {
method: 'GET',
method: "GET",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
}
},
})
.then(response => response.json())
.then(data => {
let dataList = document.getElementById(field + "Options");
dataList.innerHTML = "";
data.forEach(item => {
let option = document.createElement("option");
option.value = item.name;
option.dataset.code = item.code;
dataList.appendChild(option);
});
})
.catch(error => console.error("Error fetching options:", error));
.then((response) => response.json())
.then((data) => {
let dataList = document.getElementById(field + "Options");
dataList.innerHTML = "";
data.forEach((item) => {
let option = document.createElement("option");
option.value = item.name;
option.dataset.code = item.code;
dataList.appendChild(option);
});
})
.catch((error) => console.error("Error fetching options:", error));
};
document.querySelector('.btn-back').addEventListener('click', function() {
document.querySelector(".btn-back").addEventListener("click", function () {
window.history.back();
});
});
});

View File

@@ -18,58 +18,61 @@ console.log(dropzonePreviewNode);
url: `${GlobalConfig.apiHost}/api/advertisements/import`,
// url: "https://httpbin.org/post",
method: "post",
acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation
acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation
previewTemplate: previewTemplate,
previewsContainer: "#dropzone-preview",
autoProcessQueue: false, // Disable auto post
autoProcessQueue: false, // Disable auto post
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`
.getAttribute("content")}`,
},
init: function() {
init: function () {
// Listen for the success event
this.on("success", function(file, response) {
this.on("success", function (file, response) {
console.log("File successfully uploaded:", file);
console.log("API Response:", response);
// Show success toast
showToast('bxs-check-square', 'green', response.message);
document.getElementById("submit-upload").innerHTML = "Upload Files";
showToast("bxs-check-square", "green", response.message);
document.getElementById("submit-upload").innerHTML =
"Upload Files";
// Tunggu sebentar lalu reload halaman
setTimeout(() => {
window.location.href = "/data/advertisements";
window.location.href = "/data/web-advertisements";
}, 2000);
});
// Listen for the error event
this.on("error", function(file, errorMessage) {
this.on("error", function (file, errorMessage) {
console.error("Error uploading file:", file);
console.error("Error message:", errorMessage);
// Handle the error response
// Show error toast
showToast('bxs-error-alt', 'red', errorMessage.message);
document.getElementById("submit-upload").innerHTML = "Upload Files";
showToast("bxs-error-alt", "red", errorMessage.message);
document.getElementById("submit-upload").innerHTML =
"Upload Files";
});
}
},
})));
// Add event listener to control the submission manually
document.querySelector("#submit-upload").addEventListener("click", function() {
document.querySelector("#submit-upload").addEventListener("click", function () {
console.log("Ini adalah value dropzone", dropzone.files[0]);
const formData = new FormData()
console.log("Dropzonefiles",dropzone.files);
const formData = new FormData();
console.log("Dropzonefiles", dropzone.files);
this.innerHTML = '<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>Loading...';
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) {
formData.append('file', dropzone.files[0])
formData.append("file", dropzone.files[0]);
console.log("ini adalah form data on submit", ...formData);
dropzone.processQueue(); // Ini akan manual memicu upload
dropzone.processQueue(); // Ini akan manual memicu upload
} else {
// Show error toast when no file is selected
showToast('bxs-error-alt', 'red', "Please add a file first.");
showToast("bxs-error-alt", "red", "Please add a file first.");
document.getElementById("submit-upload").innerHTML = "Upload Files";
}
});
@@ -82,62 +85,68 @@ dropzone.on("addedfile", function (file) {
console.log("Ukuran File:", (file.size / 1024).toFixed(2) + " KB");
});
dropzone.on("complete", function(file) {
dropzone.on("complete", function (file) {
dropzone.removeFile(file);
});
// Add event listener to donwload file template
document.getElementById('downloadtempadvertisement').addEventListener('click', function() {
var url = `${GlobalConfig.apiHost}/api/download-template-advertisement`;
fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`
},
})
.then(response => {
if (response.ok) {
return response.blob();
} else {
return response.json();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'template_reklame.xlsx';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
})
.catch((error) => {
console.error("Gagal mendownload file:", error);
showToast('bxs-error-alt', 'red', "Template file is not already exist.");
})
})
document
.getElementById("downloadtempadvertisement")
.addEventListener("click", function () {
var url = `${GlobalConfig.apiHost}/api/download-template-advertisement`;
fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
})
.then((response) => {
if (response.ok) {
return response.blob();
} else {
return response.json();
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.style.display = "none";
a.href = url;
a.download = "template_reklame.xlsx";
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
})
.catch((error) => {
console.error("Gagal mendownload file:", error);
showToast(
"bxs-error-alt",
"red",
"Template file is not already exist."
);
});
});
// Function to show toast
function showToast(iconClass, iconColor, message) {
const toastElement = document.getElementById('toastUploadAdvertisement');
const toastBody = toastElement.querySelector('.toast-body');
const toastHeader = toastElement.querySelector('.toast-header');
const toastElement = document.getElementById("toastUploadAdvertisement");
const toastBody = toastElement.querySelector(".toast-body");
const toastHeader = toastElement.querySelector(".toast-header");
// Remove existing icon (if any) before adding the new one
const existingIcon = toastHeader.querySelector('.bx');
const existingIcon = toastHeader.querySelector(".bx");
if (existingIcon) {
toastHeader.querySelector('.auth-logo').removeChild(existingIcon); // Remove the existing icon
toastHeader.querySelector(".auth-logo").removeChild(existingIcon); // Remove the existing icon
}
// Add the new icon to the toast header
const icon = document.createElement('i');
icon.classList.add('bx', iconClass);
icon.style.fontSize = '25px';
const icon = document.createElement("i");
icon.classList.add("bx", iconClass);
icon.style.fontSize = "25px";
icon.style.color = iconColor;
toastHeader.querySelector('.auth-logo').appendChild(icon);
toastHeader.querySelector(".auth-logo").appendChild(icon);
// Set the toast message
toastBody.textContent = message;
@@ -146,4 +155,3 @@ function showToast(iconClass, iconColor, message) {
const toast = new bootstrap.Toast(toastElement); // Inisialisasi Bootstrap Toast
toast.show();
}

View File

@@ -73,7 +73,7 @@ document.addEventListener("DOMContentLoaded", function () {
}, 3000);
setTimeout(() => {
window.location.href = "/data/spatial-plannings";
window.location.href = "/data/web-spatial-plannings";
}, 3000);
} else {
if (authLogo) {

View File

@@ -18,58 +18,61 @@ console.log(dropzonePreviewNode);
url: `${GlobalConfig.apiHost}/api/spatial-plannings/import`,
// url: "https://httpbin.org/post",
method: "post",
acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation
acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation
previewTemplate: previewTemplate,
previewsContainer: "#dropzone-preview",
autoProcessQueue: false, // Disable auto post
autoProcessQueue: false, // Disable auto post
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`
.getAttribute("content")}`,
},
init: function() {
init: function () {
// Listen for the success event
this.on("success", function(file, response) {
this.on("success", function (file, response) {
console.log("File successfully uploaded:", file);
console.log("API Response:", response);
// Show success toast
showToast('bxs-check-square', 'green', response.message);
document.getElementById("submit-upload").innerHTML = "Upload Files";
showToast("bxs-check-square", "green", response.message);
document.getElementById("submit-upload").innerHTML =
"Upload Files";
// Tunggu sebentar lalu reload halaman
setTimeout(() => {
window.location.href = "/data/spatial-plannings";
window.location.href = "/data/web-spatial-plannings";
}, 2000);
});
// Listen for the error event
this.on("error", function(file, errorMessage) {
this.on("error", function (file, errorMessage) {
console.error("Error uploading file:", file);
console.error("Error message:", errorMessage);
// Handle the error response
// Show error toast
showToast('bxs-error-alt', 'red', errorMessage.message);
document.getElementById("submit-upload").innerHTML = "Upload Files";
showToast("bxs-error-alt", "red", errorMessage.message);
document.getElementById("submit-upload").innerHTML =
"Upload Files";
});
}
},
})));
// Add event listener to control the submission manually
document.querySelector("#submit-upload").addEventListener("click", function() {
document.querySelector("#submit-upload").addEventListener("click", function () {
console.log("Ini adalah value dropzone", dropzone.files[0]);
const formData = new FormData()
console.log("Dropzonefiles",dropzone.files);
const formData = new FormData();
console.log("Dropzonefiles", dropzone.files);
this.innerHTML = '<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>Loading...';
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) {
formData.append('file', dropzone.files[0])
formData.append("file", dropzone.files[0]);
console.log("ini adalah form data on submit", ...formData);
dropzone.processQueue(); // Ini akan manual memicu upload
dropzone.processQueue(); // Ini akan manual memicu upload
} else {
// Show error toast when no file is selected
showToast('bxs-error-alt', 'red', "Please add a file first.");
showToast("bxs-error-alt", "red", "Please add a file first.");
document.getElementById("submit-upload").innerHTML = "Upload Files";
}
});
@@ -82,63 +85,69 @@ dropzone.on("addedfile", function (file) {
console.log("Ukuran File:", (file.size / 1024).toFixed(2) + " KB");
});
dropzone.on("complete", function(file) {
dropzone.on("complete", function (file) {
dropzone.removeFile(file);
});
// Add event listener to download file template
document.getElementById('downloadtempspatialPlannings').addEventListener('click', function() {
var url = `${GlobalConfig.apiHost}/api/download-template-spatialPlannings`;
fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`
},
})
.then(response => {
if (response.ok) {
return response.blob(); // Jika respons OK, konversi menjadi blob
} else {
return response.json(); // Jika respons gagal, konversi menjadi JSON untuk menangani pesan error
}
})
.then((blob) => {
console.log(blob);
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'template_rencana_tata_ruang.xlsx';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
})
.catch((error) => {
console.error("Gagal mendownload file:", error);
showToast('bxs-error-alt', 'red', "Template file is not already exist.");
})
})
document
.getElementById("downloadtempspatialPlannings")
.addEventListener("click", function () {
var url = `${GlobalConfig.apiHost}/api/download-template-spatialPlannings`;
fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
})
.then((response) => {
if (response.ok) {
return response.blob(); // Jika respons OK, konversi menjadi blob
} else {
return response.json(); // Jika respons gagal, konversi menjadi JSON untuk menangani pesan error
}
})
.then((blob) => {
console.log(blob);
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.style.display = "none";
a.href = url;
a.download = "template_rencana_tata_ruang.xlsx";
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
})
.catch((error) => {
console.error("Gagal mendownload file:", error);
showToast(
"bxs-error-alt",
"red",
"Template file is not already exist."
);
});
});
// Function to show toast
function showToast(iconClass, iconColor, message) {
const toastElement = document.getElementById('toastUploadSpatialPlannings');
const toastBody = toastElement.querySelector('.toast-body');
const toastHeader = toastElement.querySelector('.toast-header');
const toastElement = document.getElementById("toastUploadSpatialPlannings");
const toastBody = toastElement.querySelector(".toast-body");
const toastHeader = toastElement.querySelector(".toast-header");
// Remove existing icon (if any) before adding the new one
const existingIcon = toastHeader.querySelector('.bx');
const existingIcon = toastHeader.querySelector(".bx");
if (existingIcon) {
toastHeader.querySelector('.auth-logo').removeChild(existingIcon); // Remove the existing icon
toastHeader.querySelector(".auth-logo").removeChild(existingIcon); // Remove the existing icon
}
// Add the new icon to the toast header
const icon = document.createElement('i');
icon.classList.add('bx', iconClass);
icon.style.fontSize = '25px';
const icon = document.createElement("i");
icon.classList.add("bx", iconClass);
icon.style.fontSize = "25px";
icon.style.color = iconColor;
toastHeader.querySelector('.auth-logo').appendChild(icon);
toastHeader.querySelector(".auth-logo").appendChild(icon);
// Set the toast message
toastBody.textContent = message;
@@ -147,4 +156,3 @@ function showToast(iconClass, iconColor, message) {
const toast = new bootstrap.Toast(toastElement); // Inisialisasi Bootstrap Toast
toast.show();
}

View File

@@ -73,7 +73,7 @@ document.addEventListener("DOMContentLoaded", function () {
}, 3000);
setTimeout(() => {
window.location.href = "/data/tourisms";
window.location.href = "/data/web-tourisms";
}, 3000);
} else {
if (authLogo) {

View File

@@ -18,58 +18,61 @@ console.log(dropzonePreviewNode);
url: `${GlobalConfig.apiHost}/api/tourisms/import`,
// url: "https://httpbin.org/post",
method: "post",
acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation
acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation
previewTemplate: previewTemplate,
previewsContainer: "#dropzone-preview",
autoProcessQueue: false, // Disable auto post
autoProcessQueue: false, // Disable auto post
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`
.getAttribute("content")}`,
},
init: function() {
init: function () {
// Listen for the success event
this.on("success", function(file, response) {
this.on("success", function (file, response) {
console.log("File successfully uploaded:", file);
console.log("API Response:", response);
// Show success toast
showToast('bxs-check-square', 'green', response.message);
document.getElementById("submit-upload").innerHTML = "Upload Files";
showToast("bxs-check-square", "green", response.message);
document.getElementById("submit-upload").innerHTML =
"Upload Files";
// Tunggu sebentar lalu reload halaman
setTimeout(() => {
window.location.href = "/data/tourisms";
window.location.href = "/data/web-tourisms";
}, 2000);
});
// Listen for the error event
this.on("error", function(file, errorMessage) {
this.on("error", function (file, errorMessage) {
console.error("Error uploading file:", file);
console.error("Error message:", errorMessage);
// Handle the error response
// Show error toast
showToast('bxs-error-alt', 'red', errorMessage.message);
document.getElementById("submit-upload").innerHTML = "Upload Files";
showToast("bxs-error-alt", "red", errorMessage.message);
document.getElementById("submit-upload").innerHTML =
"Upload Files";
});
}
},
})));
// Add event listener to control the submission manually
document.querySelector("#submit-upload").addEventListener("click", function() {
document.querySelector("#submit-upload").addEventListener("click", function () {
console.log("Ini adalah value dropzone", dropzone.files[0]);
const formData = new FormData()
console.log("Dropzonefiles",dropzone.files);
const formData = new FormData();
console.log("Dropzonefiles", dropzone.files);
this.innerHTML = '<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>Loading...';
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) {
formData.append('file', dropzone.files[0])
formData.append("file", dropzone.files[0]);
console.log("ini adalah form data on submit", ...formData);
dropzone.processQueue(); // Ini akan manual memicu upload
dropzone.processQueue(); // Ini akan manual memicu upload
} else {
// Show error toast when no file is selected
showToast('bxs-error-alt', 'red', "Please add a file first.");
showToast("bxs-error-alt", "red", "Please add a file first.");
document.getElementById("submit-upload").innerHTML = "Upload Files";
}
});
@@ -82,63 +85,69 @@ dropzone.on("addedfile", function (file) {
console.log("Ukuran File:", (file.size / 1024).toFixed(2) + " KB");
});
dropzone.on("complete", function(file) {
dropzone.on("complete", function (file) {
dropzone.removeFile(file);
});
// Add event listener to download file template
document.getElementById('downloadtemptourisms').addEventListener('click', function() {
var url = `${GlobalConfig.apiHost}/api/download-template-tourism`;
fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`
},
})
.then(response => {
if (response.ok) {
return response.blob(); // Jika respons OK, konversi menjadi blob
} else {
return response.json(); // Jika respons gagal, konversi menjadi JSON untuk menangani pesan error
}
})
.then((blob) => {
console.log(blob);
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'template_pariwisata.xlsx';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
})
.catch((error) => {
console.error("Gagal mendownload file:", error);
showToast('bxs-error-alt', 'red', "Template file is not already exist.");
})
})
document
.getElementById("downloadtemptourisms")
.addEventListener("click", function () {
var url = `${GlobalConfig.apiHost}/api/download-template-tourism`;
fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
})
.then((response) => {
if (response.ok) {
return response.blob(); // Jika respons OK, konversi menjadi blob
} else {
return response.json(); // Jika respons gagal, konversi menjadi JSON untuk menangani pesan error
}
})
.then((blob) => {
console.log(blob);
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.style.display = "none";
a.href = url;
a.download = "template_pariwisata.xlsx";
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
})
.catch((error) => {
console.error("Gagal mendownload file:", error);
showToast(
"bxs-error-alt",
"red",
"Template file is not already exist."
);
});
});
// Function to show toast
function showToast(iconClass, iconColor, message) {
const toastElement = document.getElementById('toastUploadTourisms');
const toastBody = toastElement.querySelector('.toast-body');
const toastHeader = toastElement.querySelector('.toast-header');
const toastElement = document.getElementById("toastUploadTourisms");
const toastBody = toastElement.querySelector(".toast-body");
const toastHeader = toastElement.querySelector(".toast-header");
// Remove existing icon (if any) before adding the new one
const existingIcon = toastHeader.querySelector('.bx');
const existingIcon = toastHeader.querySelector(".bx");
if (existingIcon) {
toastHeader.querySelector('.auth-logo').removeChild(existingIcon); // Remove the existing icon
toastHeader.querySelector(".auth-logo").removeChild(existingIcon); // Remove the existing icon
}
// Add the new icon to the toast header
const icon = document.createElement('i');
icon.classList.add('bx', iconClass);
icon.style.fontSize = '25px';
const icon = document.createElement("i");
icon.classList.add("bx", iconClass);
icon.style.fontSize = "25px";
icon.style.color = iconColor;
toastHeader.querySelector('.auth-logo').appendChild(icon);
toastHeader.querySelector(".auth-logo").appendChild(icon);
// Set the toast message
toastBody.textContent = message;
@@ -147,4 +156,3 @@ function showToast(iconClass, iconColor, message) {
const toast = new bootstrap.Toast(toastElement); // Inisialisasi Bootstrap Toast
toast.show();
}

View File

@@ -16,10 +16,10 @@ document.addEventListener("DOMContentLoaded", function () {
Loading...
`;
const isEdit = saveButton.classList.contains("btn-edit");
const formData = new FormData(form)
const toast = document.getElementById('toastEditUpdate');
const toastBody = toast.querySelector('.toast-body');
const toastHeader = toast.querySelector('.toast-header small');
const formData = new FormData(form);
const toast = document.getElementById("toastEditUpdate");
const toastBody = toast.querySelector(".toast-body");
const toastHeader = toast.querySelector(".toast-header small");
const data = {};
@@ -40,53 +40,88 @@ document.addEventListener("DOMContentLoaded", function () {
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
}
},
})
.then(response => response.json())
.then(data => {
console.log("Response data:", data);
if (!data.errors) {
// Remove existing icon (if any) before adding the new one
if (authLogo) {
// Hapus ikon yang sudah ada jika ada
const existingIcon = authLogo.querySelector('.bx');
if (existingIcon) {
authLogo.removeChild(existingIcon);
.then((response) => response.json())
.then((data) => {
console.log("Response data:", data);
if (!data.errors) {
// Remove existing icon (if any) before adding the new one
if (authLogo) {
// Hapus ikon yang sudah ada jika ada
const existingIcon = authLogo.querySelector(".bx");
if (existingIcon) {
authLogo.removeChild(existingIcon);
}
// Buat ikon baru
const icon = document.createElement("i");
icon.classList.add("bx", "bxs-check-square");
icon.style.fontSize = "25px";
icon.style.color = "green"; // Pastikan 'green' dalam bentuk string
// Tambahkan ikon ke dalam auth-logo
authLogo.appendChild(icon);
}
// Buat ikon baru
const icon = document.createElement('i');
icon.classList.add('bx', 'bxs-check-square');
icon.style.fontSize = '25px';
icon.style.color = 'green'; // Pastikan 'green' dalam bentuk string
// Set success message for the toast
toastBody.textContent = isEdit
? "Data updated successfully!"
: "Data created successfully!";
toast.classList.add("show"); // Show the toast
setTimeout(() => {
toast.classList.remove("show"); // Hide the toast after 3 seconds
}, 2000);
// Tambahkan ikon ke dalam auth-logo
authLogo.appendChild(icon);
setTimeout(() => {
window.location.href = "/data/web-umkm";
}, 1000);
} else {
if (authLogo) {
// Hapus ikon yang sudah ada jika ada
const existingIcon = authLogo.querySelector(".bx");
if (existingIcon) {
authLogo.removeChild(existingIcon);
}
// Buat ikon baru
const icon = document.createElement("i");
icon.classList.add("bx", "bxs-error-alt");
icon.style.fontSize = "25px";
icon.style.color = "red"; // Pastikan 'green' dalam bentuk string
// Tambahkan ikon ke dalam auth-logo
authLogo.appendChild(icon);
}
// Enable button and reset its text on error
modalButton.disabled = false;
modalButton.innerHTML = isEdit ? "Update" : "Create";
// Set error message for the toast
toastBody.textContent =
"Error: " + (data.message || "Something went wrong");
toast.classList.add("show"); // Show the toast
setTimeout(() => {
toast.classList.remove("show"); // Hide the toast after 3 seconds
}, 3000);
}
// Set success message for the toast
toastBody.textContent = isEdit ? "Data updated successfully!" : "Data created successfully!";
toast.classList.add('show'); // Show the toast
setTimeout(() => {
toast.classList.remove('show'); // Hide the toast after 3 seconds
}, 2000);
setTimeout(() => {
window.location.href = '/data/umkm';
}, 1000);
} else {
})
.catch((error) => {
console.error("Error:", error);
if (authLogo) {
// Hapus ikon yang sudah ada jika ada
const existingIcon = authLogo.querySelector('.bx');
const existingIcon = authLogo.querySelector(".bx");
if (existingIcon) {
authLogo.removeChild(existingIcon);
}
// Buat ikon baru
const icon = document.createElement('i');
icon.classList.add('bx', 'bxs-error-alt');
icon.style.fontSize = '25px';
icon.style.color = 'red'; // Pastikan 'green' dalam bentuk string
const icon = document.createElement("i");
icon.classList.add("bx", "bxs-error-alt");
icon.style.fontSize = "25px";
icon.style.color = "red"; // Pastikan 'green' dalam bentuk string
// Tambahkan ikon ke dalam auth-logo
authLogo.appendChild(icon);
@@ -96,47 +131,15 @@ document.addEventListener("DOMContentLoaded", function () {
modalButton.disabled = false;
modalButton.innerHTML = isEdit ? "Update" : "Create";
// Set error message for the toast
toastBody.textContent = "Error: " + (data.message || "Something went wrong");
toast.classList.add('show'); // Show the toast
toastBody.textContent =
"An error occurred while processing your request.";
toast.classList.add("show"); // Show the toast
setTimeout(() => {
toast.classList.remove('show'); // Hide the toast after 3 seconds
toast.classList.remove("show"); // Hide the toast after 3 seconds
}, 3000);
}
})
.catch(error => {
console.error("Error:", error);
if (authLogo) {
// Hapus ikon yang sudah ada jika ada
const existingIcon = authLogo.querySelector('.bx');
if (existingIcon) {
authLogo.removeChild(existingIcon);
}
// Buat ikon baru
const icon = document.createElement('i');
icon.classList.add('bx', 'bxs-error-alt');
icon.style.fontSize = '25px';
icon.style.color = 'red'; // Pastikan 'green' dalam bentuk string
// Tambahkan ikon ke dalam auth-logo
authLogo.appendChild(icon);
}
// Enable button and reset its text on error
modalButton.disabled = false;
modalButton.innerHTML = isEdit ? "Update" : "Create";
// Set error message for the toast
toastBody.textContent = "An error occurred while processing your request.";
toast.classList.add('show'); // Show the toast
setTimeout(() => {
toast.classList.remove('show'); // Hide the toast after 3 seconds
}, 3000);
});
});
});
// Fungsi fetchOptions untuk autocomplete server-side
@@ -145,7 +148,11 @@ document.addEventListener("DOMContentLoaded", function () {
if (inputValue.length < 2) return;
let districtValue = document.getElementById("district_name").value; // Ambil kecamatan terpilih
let url = `${GlobalConfig.apiHost}/api/combobox/search-options?query=${encodeURIComponent(inputValue)}&field=${field}`;
let url = `${
GlobalConfig.apiHost
}/api/combobox/search-options?query=${encodeURIComponent(
inputValue
)}&field=${field}`;
// Jika field desa, tambahkan kecamatan sebagai filter
if (field === "village_name") {
@@ -153,30 +160,30 @@ document.addEventListener("DOMContentLoaded", function () {
}
fetch(url, {
method: 'GET',
method: "GET",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
}
},
})
.then(response => response.json())
.then(data => {
let dataList = document.getElementById(field + "Options");
dataList.innerHTML = "";
.then((response) => response.json())
.then((data) => {
let dataList = document.getElementById(field + "Options");
dataList.innerHTML = "";
data.forEach(item => {
let option = document.createElement("option");
option.value = item.name;
option.dataset.code = item.code;
dataList.appendChild(option);
});
})
.catch(error => console.error("Error fetching options:", error));
data.forEach((item) => {
let option = document.createElement("option");
option.value = item.name;
option.dataset.code = item.code;
dataList.appendChild(option);
});
})
.catch((error) => console.error("Error fetching options:", error));
};
document.querySelector('.btn-back').addEventListener('click', function() {
document.querySelector(".btn-back").addEventListener("click", function () {
window.history.back();
});
});
});

View File

@@ -18,58 +18,61 @@ console.log(dropzonePreviewNode);
url: `${GlobalConfig.apiHost}/api/umkm/import`,
// url: "https://httpbin.org/post",
method: "post",
acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation
acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation
previewTemplate: previewTemplate,
previewsContainer: "#dropzone-preview",
autoProcessQueue: false, // Disable auto post
autoProcessQueue: false, // Disable auto post
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`
.getAttribute("content")}`,
},
init: function() {
init: function () {
// Listen for the success event
this.on("success", function(file, response) {
this.on("success", function (file, response) {
console.log("File successfully uploaded:", file);
console.log("API Response:", response);
// Show success toast
showToast('bxs-check-square', 'green', response.message);
document.getElementById("submit-upload").innerHTML = "Upload Files";
showToast("bxs-check-square", "green", response.message);
document.getElementById("submit-upload").innerHTML =
"Upload Files";
// Tunggu sebentar lalu reload halaman
setTimeout(() => {
window.location.href = "/data/umkm";
window.location.href = "/data/web-umkm";
}, 2000);
});
// Listen for the error event
this.on("error", function(file, errorMessage) {
this.on("error", function (file, errorMessage) {
console.error("Error uploading file:", file);
console.error("Error message:", errorMessage);
// Handle the error response
// Show error toast
showToast('bxs-error-alt', 'red', errorMessage.message);
document.getElementById("submit-upload").innerHTML = "Upload Files";
showToast("bxs-error-alt", "red", errorMessage.message);
document.getElementById("submit-upload").innerHTML =
"Upload Files";
});
}
},
})));
// Add event listener to control the submission manually
document.querySelector("#submit-upload").addEventListener("click", function() {
document.querySelector("#submit-upload").addEventListener("click", function () {
console.log("Ini adalah value dropzone", dropzone.files[0]);
const formData = new FormData()
console.log("Dropzonefiles",dropzone.files);
const formData = new FormData();
console.log("Dropzonefiles", dropzone.files);
this.innerHTML = '<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>Loading...';
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) {
formData.append('file', dropzone.files[0])
formData.append("file", dropzone.files[0]);
console.log("ini adalah form data on submit", ...formData);
dropzone.processQueue(); // Ini akan manual memicu upload
dropzone.processQueue(); // Ini akan manual memicu upload
} else {
// Show error toast when no file is selected
showToast('bxs-error-alt', 'red', "Please add a file first.");
showToast("bxs-error-alt", "red", "Please add a file first.");
document.getElementById("submit-upload").innerHTML = "Upload Files";
}
});
@@ -82,62 +85,68 @@ dropzone.on("addedfile", function (file) {
console.log("Ukuran File:", (file.size / 1024).toFixed(2) + " KB");
});
dropzone.on("complete", function(file) {
dropzone.on("complete", function (file) {
dropzone.removeFile(file);
});
// Add event listener to download file template
document.getElementById('downloadtempumkm').addEventListener('click', function() {
var url = `${GlobalConfig.apiHost}/api/download-template-umkm`;
fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`
},
})
.then(response => {
if (response.ok) {
return response.blob(); // Jika respons OK, konversi menjadi blob
} else {
return response.json(); // Jika respons gagal, konversi menjadi JSON untuk menangani pesan error
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'template_umkm.xlsx';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
})
.catch((error) => {
console.error("Gagal mendownload file:", error);
showToast('bxs-error-alt', 'red', "Template file is not already exist.");
})
})
document
.getElementById("downloadtempumkm")
.addEventListener("click", function () {
var url = `${GlobalConfig.apiHost}/api/download-template-umkm`;
fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
})
.then((response) => {
if (response.ok) {
return response.blob(); // Jika respons OK, konversi menjadi blob
} else {
return response.json(); // Jika respons gagal, konversi menjadi JSON untuk menangani pesan error
}
})
.then((blob) => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.style.display = "none";
a.href = url;
a.download = "template_umkm.xlsx";
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
})
.catch((error) => {
console.error("Gagal mendownload file:", error);
showToast(
"bxs-error-alt",
"red",
"Template file is not already exist."
);
});
});
// Function to show toast
function showToast(iconClass, iconColor, message) {
const toastElement = document.getElementById('toastUploadUmkm');
const toastBody = toastElement.querySelector('.toast-body');
const toastHeader = toastElement.querySelector('.toast-header');
const toastElement = document.getElementById("toastUploadUmkm");
const toastBody = toastElement.querySelector(".toast-body");
const toastHeader = toastElement.querySelector(".toast-header");
// Remove existing icon (if any) before adding the new one
const existingIcon = toastHeader.querySelector('.bx');
const existingIcon = toastHeader.querySelector(".bx");
if (existingIcon) {
toastHeader.querySelector('.auth-logo').removeChild(existingIcon); // Remove the existing icon
toastHeader.querySelector(".auth-logo").removeChild(existingIcon); // Remove the existing icon
}
// Add the new icon to the toast header
const icon = document.createElement('i');
icon.classList.add('bx', iconClass);
icon.style.fontSize = '25px';
const icon = document.createElement("i");
icon.classList.add("bx", iconClass);
icon.style.fontSize = "25px";
icon.style.color = iconColor;
toastHeader.querySelector('.auth-logo').appendChild(icon);
toastHeader.querySelector(".auth-logo").appendChild(icon);
// Set the toast message
toastBody.textContent = message;
@@ -146,4 +155,3 @@ function showToast(iconClass, iconColor, message) {
const toast = new bootstrap.Toast(toastElement); // Inisialisasi Bootstrap Toast
toast.show();
}

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: {
limit: 15,
limit: 50,
server: {
url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${
@@ -44,6 +44,7 @@ class UsersTable {
server: {
url: (prev, keyword) => `${prev}?search=${keyword}`,
},
debounceTimeout: 1000,
},
server: {
url: `${GlobalConfig.apiHost}/api/users`,

View File

@@ -28,35 +28,35 @@ class Menus {
initTableMenus() {
let tableContainer = document.getElementById("table-menus");
if (this.table) {
// If table exists, update its data instead of recreating
this.table
.updateConfig({
server: {
url: `${GlobalConfig.apiHost}/api/menus`,
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
},
then: (data) =>
data.data.map((item) => [
item.id,
item.name,
item.url,
item.icon,
item.parent_id,
item.sort_order,
item.id,
]),
total: (data) => data.total,
},
})
.forceRender();
return;
}
// if (this.table) {
// // If table exists, update its data instead of recreating
// this.table
// .updateConfig({
// server: {
// url: `${GlobalConfig.apiHost}/api/menus`,
// credentials: "include",
// headers: {
// Authorization: `Bearer ${document
// .querySelector('meta[name="api-token"]')
// .getAttribute("content")}`,
// "Content-Type": "application/json",
// },
// then: (data) =>
// data.data.map((item) => [
// item.id,
// item.name,
// item.url,
// item.icon,
// item.parent_id,
// item.sort_order,
// item.id,
// ]),
// total: (data) => data.total,
// },
// })
// .forceRender();
// return;
// }
this.table = new Grid({
columns: [
@@ -83,7 +83,7 @@ class Menus {
},
],
pagination: {
limit: 15,
limit: 50,
server: {
url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${
@@ -96,6 +96,7 @@ class Menus {
server: {
url: (prev, keyword) => `${prev}?search=${keyword}`,
},
debounceTimeout: 1000,
},
server: {
url: `${GlobalConfig.apiHost}/api/menus`,

View File

@@ -36,6 +36,7 @@ class PbgTasks {
server: {
url: (prev, keyword) => `${prev}?search=${keyword}`,
},
debounceTimeout: 1000,
},
pagination: {
limit: 15,

View File

@@ -0,0 +1,69 @@
import { Grid } from "gridjs/dist/gridjs.umd.js";
import "gridjs/dist/gridjs.umd.js";
import gridjs from "gridjs/dist/gridjs.umd.js";
import GlobalConfig from "../global-config";
class PbgTaskAssignments {
init() {
this.initTablePbgTaskAssignments();
}
initTablePbgTaskAssignments() {
let tableContainer = document.getElementById(
"table-pbg-task-assignments"
);
let uuid = document.getElementById("uuid").value;
new Grid({
columns: [
"ID",
"Nama",
"Email",
"Nomor Telepon",
"Keahlian",
"Status",
],
search: {
server: {
url: (prev, keyword) => `${prev}?search=${keyword}`,
},
debounceTimeout: 1000,
},
pagination: {
limit: 15,
server: {
url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${
page + 1
}`,
},
},
sort: true,
server: {
url: `${GlobalConfig.apiHost}/api/task-assignments/${uuid}`,
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
},
then: (data) =>
data.data.map((item) => [
item.id,
item.name,
item.email,
item.phone_number,
item.expertise,
item.status_name,
]),
total: (data) => data.meta.total,
},
}).render(tableContainer);
}
}
document.addEventListener("DOMContentLoaded", function (e) {
new PbgTaskAssignments().init();
});

View File

@@ -52,7 +52,7 @@ class Roles {
},
],
pagination: {
limit: 15,
limit: 50,
server: {
url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${
@@ -65,6 +65,7 @@ class Roles {
server: {
url: (prev, keyword) => `${prev}?search=${keyword}`,
},
debounceTimeout: 1000,
},
server: {
url: `${GlobalConfig.apiHost}/api/roles`,

View File

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

View File

@@ -18,12 +18,15 @@ class GeneralTable {
server: {
url: (prev, keyword) => `${prev}?search=${keyword}`,
},
debounceTimeout: 1000,
},
pagination: this.options.pagination || {
limit: 15,
server: {
url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${page + 1}`,
`${prev}${prev.includes("?") ? "&" : "?"}page=${
page + 1
}`,
},
},
sort: this.options.sort || true,
@@ -48,22 +51,29 @@ class GeneralTable {
processData(data) {
return data.data.map((item) => {
return this.columns.map((column) => {
return item[column] || '';
return item[column] || "";
});
});
}
handleActions() {
document.addEventListener("click", (event) => {
if (event.target && event.target.classList.contains('btn-edit')) {
if (event.target && event.target.classList.contains("btn-edit")) {
this.handleEdit(event);
}
else if (event.target && event.target.classList.contains('btn-delete')) {
} else if (
event.target &&
event.target.classList.contains("btn-delete")
) {
this.handleDelete(event);
}
else if (event.target && event.target.classList.contains('btn-create')) {
} else if (
event.target &&
event.target.classList.contains("btn-create")
) {
this.handleCreate(event);
} else if (event.target && event.target.classList.contains('btn-bulk-create')) {
} else if (
event.target &&
event.target.classList.contains("btn-bulk-create")
) {
this.handleBulkCreate(event);
}
});
@@ -72,28 +82,28 @@ class GeneralTable {
// Fungsi untuk menangani create
handleCreate(event) {
// Menggunakan model dan ID untuk membangun URL dinamis
const model = event.target.getAttribute('data-model'); // Mengambil model dari data-model
const model = event.target.getAttribute("data-model"); // Mengambil model dari data-model
window.location.href = `${this.baseUrl}/${model}/create`;
}
handleBulkCreate(event) {
// Menggunakan model dan ID untuk membangun URL dinamis
const model = event.target.getAttribute('data-model');
const model = event.target.getAttribute("data-model");
window.location.href = `${this.baseUrl}/${model}/bulk-create`;
}
// Fungsi untuk menangani edit
handleEdit(event) {
const id = event.target.getAttribute('data-id');
const model = event.target.getAttribute('data-model'); // Mengambil model dari data-model
console.log('Editing record with ID:', id);
const id = event.target.getAttribute("data-id");
const model = event.target.getAttribute("data-model"); // Mengambil model dari data-model
console.log("Editing record with ID:", id);
// Menggunakan model dan ID untuk membangun URL dinamis
window.location.href = `${this.baseUrl}/${model}/${id}/edit`;
}
// Fungsi untuk menangani delete
handleDelete(event) {
const id = event.target.getAttribute('data-id');
const id = event.target.getAttribute("data-id");
console.log(id);
// if (confirm("Are you sure you want to delete this item?")) {
// this.deleteRecord(id);
@@ -105,7 +115,7 @@ class GeneralTable {
showCancelButton: true,
confirmButtonColor: "#d33",
cancelButtonColor: "#3085d6",
confirmButtonText: "Yes, delete it!"
confirmButtonText: "Yes, delete it!",
}).then((result) => {
if (result.isConfirmed) {
this.deleteRecord(id);
@@ -114,8 +124,8 @@ class GeneralTable {
text: "Your record has been deleted.",
icon: "success",
showConfirmButton: false, // Menghilangkan tombol OK
timer: 2000 // Menutup otomatis dalam 2 detik (opsional)
});
timer: 2000, // Menutup otomatis dalam 2 detik (opsional)
});
}
});
}
@@ -123,8 +133,9 @@ class GeneralTable {
async deleteRecord(id) {
try {
console.log(id);
const response = await fetch(`${this.apiUrl}/${id}`, { // Menambahkan model dalam URL
method: 'DELETE',
const response = await fetch(`${this.apiUrl}/${id}`, {
// Menambahkan model dalam URL
method: "DELETE",
headers: this.options.headers || {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
@@ -136,10 +147,14 @@ class GeneralTable {
location.reload();
} else {
const data = await response.json();
showErrorAlert(`Failed to delete record: ${data.message || "Unknown error"}`);
showErrorAlert(
`Failed to delete record: ${
data.message || "Unknown error"
}`
);
}
} catch (error) {
console.error('Error deleting data:', error);
console.error("Error deleting data:", error);
showErrorAlert("Error deleting data. Please try again.");
}
}
@@ -148,7 +163,7 @@ class GeneralTable {
// Fungsi untuk menampilkan alert
function showErrorAlert(message) {
const alertContainer = document.getElementById("alert-container");
alertContainer.innerHTML = `
<div class="alert alert-danger alert-dismissible fade show" role="alert">
${message}
@@ -157,4 +172,4 @@ function showErrorAlert(message) {
`;
}
export default GeneralTable;
export default GeneralTable;

View File

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

View File

@@ -0,0 +1,234 @@
//
// inside_system.scss
//
.square {
height: 100px;
width: 100px;
position: absolute;
z-index: -1;
}
.dia-top-left-bottom-right:after {
content: "";
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: linear-gradient(
to top right,
transparent calc(50% - 2px),
black,
transparent calc(50% + 2px)
);
}
.dia-top-right-bottom-left:after {
content: "";
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: linear-gradient(
to top left,
transparent calc(50% - 2px),
black,
transparent calc(50% + 2px)
);
}
.lack-of-potential-wrapper {
background-image: url("/public/images/bg-dashboard.jpg");
background-size: cover;
background-position: center;
background-color: rgba(255, 255, 255, 0.7);
max-width: 100vw;
}
.lack-of-potential-wrapper::before {
content: "";
position: absolute;
pointer-events: none;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.7);
}
// #lack-of-potential-fixed-container {
// min-width: 1110px;
// max-width: unset; /* Allow it to grow if needed */
// }
// @media (max-width: 768px) {
// #lack-of-potential-fixed-container {
// transform: scale(0.8); /* Adjust the scale as needed */
// }
// }
// line degrees
.line {
background-color: black;
position: absolute;
height: 3px;
}
.home-to-non-usaha {
width: 100px;
top: 13%;
left: 38%;
transform: rotate(90deg);
}
.restoran-to-bapenda {
width: 110px;
top: 14%;
left: 60%;
transform: rotate(40deg);
}
.pbb-to-bapenda {
width: 80px;
top: 21%;
left: 80%;
}
.reklame-to-bapenda {
width: 120px;
left: 75%;
top: 30%;
transform: rotateZ(142deg);
}
.non-usaha-to-bapenda {
width: 116px;
left: 18%;
top: 33%;
transform: rotateZ(124deg);
}
.non-usaha-to-pdam {
width: 100px;
left: 38%;
top: 34%;
transform: rotateZ(90deg);
}
.non-usaha-to-kecamatan {
width: 140px;
left: 55%;
top: 33%;
transform: rotateZ(237deg);
}
.bapenda-to-usaha {
width: 114px;
left: 18%;
top: 49%;
transform: rotateZ(56deg);
}
.pdam-to-usaha {
width: 88px;
left: 39%;
top: 49%;
transform: rotateZ(90deg);
}
.kecamatan-to-usaha {
width: 118px;
left: 56%;
top: 50%;
transform: rotateZ(117deg);
}
.usaha-to-villa {
width: 100px;
left: 10%;
top: 63%;
transform: rotateZ(143deg);
}
.usaha-to-pabrik {
width: 150px;
left: 15%;
top: 70%;
transform: rotateZ(143deg);
}
.usaha-to-pariwisata {
width: 150px;
left: 43%;
top: 70%;
transform: rotateZ(38deg);
}
.usaha-to-protocol {
width: 106px;
left: 36%;
top: 71%;
transform: rotateZ(86deg);
}
.pariwisata-to-disbudpar {
width: 86px;
left: 54%;
top: 83%;
transform: rotateZ(150deg);
}
.non-usaha-to-wasdal {
width: 300px;
left: -32%;
top: 34%;
transform: rotateZ(226deg);
}
.usaha-to-wasdal {
width: 300px;
left: -34%;
top: 50%;
transform: rotateZ(129deg);
}
.wasdal-to-upt {
width: 155px;
left: 3%;
top: -67%;
transform: rotateZ(127deg);
}
.wasdal-to-satpol {
width: 155px;
left: 19%;
top: -52%;
transform: rotateZ(76deg);
}
.wasdal-to-kejari {
width: 182px;
left: 25%;
top: -55%;
transform: rotateZ(51deg);
}
.wasdal-to-tni {
width: 260px;
left: 29%;
top: -62%;
transform: rotateZ(30deg);
}
.wasdal-to-potential {
width: 50px;
left: 28%;
top: 41%;
}
.potential-to-tata-ruang {
width: 50px;
left: 72%;
top: 41%;
}
.tata-ruang-to-non-usaha {
width: 220px;
left: 0%;
top: 30%;
transform: rotateZ(144deg);
}
.tata-ruang-to-usaha {
width: 280px;
left: 0%;
top: 52%;
transform: rotateZ(224deg);
}
.tata-ruang-to-peta {
width: 122px;
left: 8%;
top: 41%;
}
.peta-to-tapak {
width: 30px;
left: 47%;
top: 41%;
}

View File

@@ -0,0 +1,59 @@
//
// outside_system.scss
//
.square {
height: 100px;
width: 100px;
position: absolute;
z-index: -1;
}
.dia-top-left-bottom-right:after {
content: "";
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: linear-gradient(
to top right,
transparent calc(50% - 2px),
black,
transparent calc(50% + 2px)
);
}
.dia-top-right-bottom-left:after {
content: "";
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: linear-gradient(
to top left,
transparent calc(50% - 2px),
black,
transparent calc(50% + 2px)
);
}
.outside-system-wrapper {
background-image: url("/public/images/bg-dashboard.jpg");
background-size: cover;
background-position: center;
background-color: rgba(255, 255, 255, 0.7);
max-width: 90vw;
}
.outside-system-wrapper::before {
content: "";
position: absolute;
pointer-events: none;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.7);
}

View File

@@ -1,6 +1,6 @@
//
//
// _gridjs.scss
//
//
.gridjs-container {
color: var(--#{$prefix}body-color);
@@ -13,7 +13,6 @@
border: 1px solid var(--#{$prefix}border-color);
border-radius: 0px;
&::-webkit-scrollbar {
-webkit-appearance: none;
}
@@ -24,11 +23,12 @@
}
&::-webkit-scrollbar:horizontal {
height: 5px;
height: 15px;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(var(--#{$prefix}dark-rgb), .075);
// background-color: rgba(var(--#{$prefix}dark-rgb), 0.075);
background-color: $primary;
border-radius: 10px;
padding: 5px;
border: none;
@@ -69,15 +69,13 @@ th {
&.gridjs-th {
border-top: 0;
color: var(--#{$prefix}body-color);
background-color: rgba(var(--#{$prefix}light-rgb), .75);
background-color: rgba(var(--#{$prefix}light-rgb), 0.75);
}
&.gridjs-th-sort {
&:focus,
&:hover {
background-color: rgba(var(--#{$prefix}light-rgb), .85);
background-color: rgba(var(--#{$prefix}light-rgb), 0.85);
}
}
}
@@ -99,7 +97,6 @@ th {
}
.gridjs-pagination {
.gridjs-pages button {
background-color: transparent;
color: var(--#{$prefix}link-color);
@@ -166,7 +163,8 @@ input.gridjs-input {
background-color: $input-bg;
color: $input-color;
line-height: $input-line-height;
padding: $input-padding-y $input-padding-x $input-padding-y $input-padding-x * 2.5;
padding: $input-padding-y $input-padding-x $input-padding-y $input-padding-x *
2.5;
border-radius: $input-border-radius;
@include font-size($input-font-size);
@@ -203,30 +201,26 @@ th.gridjs-th-sort .gridjs-th-content {
}
button {
&.gridjs-sort-asc,
&.gridjs-sort-desc {
background-size: 7px;
}
}
// gridjs selection
.gridjs-tr-selected {
td {
background-color: $table-active-bg;
}
.gridjs-td .gridjs-checkbox[type=checkbox] {
.gridjs-td .gridjs-checkbox[type="checkbox"] {
background-color: $form-check-input-checked-bg-color;
border-color: $form-check-input-checked-border-color;
@if $enable-gradients {
background-image: escape-svg($form-check-input-checked-bg-image),
var(--#{$prefix}gradient);
}
@else {
var(--#{$prefix}gradient);
} @else {
background-image: escape-svg($form-check-input-checked-bg-image);
}
}
@@ -253,7 +247,6 @@ button {
}
.gridjs-border-none {
td.gridjs-td,
th.gridjs-th {
border-right-width: 0;
@@ -267,11 +260,10 @@ button {
[data-bs-theme="dark"] {
button {
&.gridjs-sort-neutral,
&.gridjs-sort-asc,
&.gridjs-sort-desc {
filter: $btn-close-white-filter;
}
}
}
}

View File

@@ -46,7 +46,8 @@
@import "components/widgets";
@import "components/circle";
@import "components/custom_circle";
@import "dashboards/lack-of-potential";
@import "dashboards/potentials/inside_system";
@import "dashboards/potentials/outside_system";
// Plugin
@import "plugins/simplebar";

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 h-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 -->
</div>
<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> <!-- end card body -->
</div> <!-- end card -->

View File

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

View File

@@ -0,0 +1,77 @@
@extends('layouts.vertical', ['subtitle' => 'Main 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' => 'Main Chatbot', 'subtitle' => 'Main Chatbot'])
<div class="card">
<div class="card-body d-flex flex-column" style="height: 700px;">
<!-- Conversation Area -->
<!-- Bot Response -->
<div class="row flex-grow overflow-auto align-items-start">
<!-- Avatar -->
<div class="col-auto alignpe-0">
<img class="rounded-circle" width="45" src="/images/iconchatbot.jpeg" alt="avatar-3">
</div>
<!-- Nama dan Bubble Chat -->
<div class="col-9 w-auto">
<!-- Nama Bot -->
<p class="fw-bolder mb-1">Neng Bedas</p>
<!-- Bubble Chat -->
<div class="bot-response p-2 bg-light rounded mb-2 d-inline-block">
<p class="mb-0">Halo! Ada yang bisa saya bantu?</p>
<!-- Waktu (Tetap di Dalam Bubble Chat) -->
<div class="sending-message-time text-end mt-1">
<p class="text-muted small mb-0">Now</p>
</div>
</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 h-100 d-flex align-items-center">
<i class='bx bx-send'></i>
</button>
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
@vite(['resources/js/chatbot-pimpinan/index.js'])
@endsection

View File

@@ -30,58 +30,65 @@
@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>
<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 active">
<span class="d-block d-sm-none"><i class="bx bx-home"></i></span>
<span class="d-none d-sm-block fs-4">Perhitungan Retribusi</span>
</button>
</li>
<li class="nav-item">
<button id="document-validation" data-bs-toggle="tab" aria-expanded="true" class="nav-link">
<span class="d-block d-sm-none"><i class="bx bx-user"></i></span>
<span class="d-none d-sm-block fs-4">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 fs-4">Pengumpulan Data PBG</span>
</button>
</li>
</ul>
{{-- <div class="card-header">
</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="row flex-grow overflow-auto align-items-start">
<!-- Avatar -->
<div class="col-auto alignpe-0">
<img class="rounded-circle" width="45" src="/images/iconchatbot.jpeg" alt="avatar-3">
</div>
<!-- Nama dan Bubble Chat -->
<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>
<!-- Nama Bot -->
<p class="fw-bolder mb-1">Neng Bedas</p>
<!-- Bubble Chat -->
<div class="bot-response p-2 bg-light rounded mb-2 d-inline-block">
<p class="mb-0">Halo! Ada yang bisa saya bantu?</p>
<!-- Waktu (Tetap di Dalam Bubble Chat) -->
<div class="sending-message-time text-end mt-1">
<p class="text-muted small mb-0">Now</p>
</div>
</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">
<button id="send" class="btn btn-primary btn-lg h-100 d-flex align-items-center">
<i class='bx bx-send'></i>
</button>
</div>
{{-- <div class="col-xl-2 d-flex justify-content-end">
</div> --}}
</div>
</div>
</div>
</div>

View File

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

View File

@@ -9,7 +9,7 @@
@include('layouts.partials/page-title', ['title' => 'Dashboards', 'subtitle' => 'Dashboard Pimpinan'])
<div id="dashboard-fixed-wrapper" class="row">
<div class="col-12">
<!-- <div class="col-12">
<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>
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" />
</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 id="dashboard-fixed-container" class="row" style="width:1110px;height:770px;position:relative;margin:auto;">
@component('components.circle', [
@@ -37,7 +49,7 @@
@endcomponent
@component('components.circle', [
'document_title' => 'Target PAD 2024',
'document_title' => 'Target PAD',
'document_color' => '#204f6b',
'document_type' => '',
'document_id' => 'chart-target-pad',

View File

@@ -9,23 +9,19 @@
<div class="lack-of-potential-wrapper">
<div class="row" id="lack-of-potential-wrapper">
<div class="col-12">
<h3 class="mt-3 ms-2 text-danger">
<div class="d-flex justify-content-between align-items-center mt-3 ms-2">
<h2 class="text-danger m-0">
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 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 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>
<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>
@@ -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-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>
<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="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="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>
<x-custom-circle title="DISBUDPAR" size="small" style="background-color: #3a968b;" />
</div>
@@ -78,23 +74,20 @@
'visible_small_circle' => false,
'style' => 'margin-left:180px;top:-20px;'
])
@endcomponent
<x-custom-circle title="Tata Ruang" size="large" style="background-color: #da6635;float:left;margin-left:250px;" />
</div>
@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" />
</div>
<div style="position: absolute; top: 310px; left: 1150px;">
<div class="square dia-top-left-bottom-right" style="top:90px;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="Tapak Bangunan" size="small" style="background-color: #2390af;float:left;margin-left:20px;" />
</div>
<div style="position: absolute; top: 310px; left: 1150px;">
<div class="square dia-top-left-bottom-right" style="top:90px;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="Tapak Bangunan" size="small" style="background-color: #2390af;float:left;margin-left:20px;" />
</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;" />
<div style="position: absolute; top: 470px; left: 430px;">
<div style="position: absolute; top: 470px; left: 430px;">
<div class="square dia-top-right-bottom-left" style="top:-80px;left:20px;width:150px;height:120px;"></div>
<div class="square dia-top-right-bottom-left" style="top:-50px;left:100px;width:100px;height:100px;"></div>
<div class="square dia-top-left-bottom-right" style="top:-50px;left:180px;width:100px;height:100px;"></div>
@@ -109,7 +102,7 @@
<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="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>

View File

@@ -0,0 +1,115 @@
@extends('layouts.vertical', ['subtitle' => 'Dashboards'])
@section('css')
@vite(['resources/scss/dashboards/potentials/_inside_system.scss'])
@endsection
@section('content')
@include('layouts.partials.page-title', ['title' => 'Dashboards', 'subtitle' => 'Dalam Sistem'])
<div class="lack-of-potential-wrapper">
<div class="row" id="lack-of-potential-wrapper">
<div class="d-flex justify-content-between align-items-center mt-3 ms-2">
<h2 class="text-danger m-0">
ANALISA BIG DATA MELALUI APLIKASI <br> SIBEDAS PBG DALAM SISTEM
</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 class="wrapper">
<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;">
<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>
<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>
<x-custom-circle title="Reklame" visible_data="true" data_id="reklame-count" data_count="0" size="small" style="background-color: #0e4753;" />
<div class="square dia-top-right-bottom-left" style="top:140px;left:50px;width:150px;height:120px;"></div>
</div>
<div style="position: absolute; top: 300px; left: 200px;">
<div class="square dia-top-right-bottom-left" style="top:-100px;left:30px;width:150px;height:120px;"></div>
<div class="square dia-top-left-bottom-right" style="top:-100px;left:120px;width:120px;height:120px;"></div>
<x-custom-circle title="BAPENDA" size="small" style="float:left;background-color: #234f6c;" />
<x-custom-circle title="PDAM" visible_data="true" data_id="pdam-count" data_count="0" visible_data_type="true" data_type="Pelanggan" size="small" style="float:left;background-color: #234f6c;" />
<x-custom-circle title="KECAMATAN" size="small" style="float:left;background-color: #234f6c;" />
</div>
<div style="position: absolute; top: 0px; left: 270px;">
<div class="square" style="width:5px;height:600px;background-color:black;left:70px;top:50px;"></div>
<div class="square dia-top-left-bottom-right" style="top:350px;left:-50px;width:120px;height:120px;"></div>
<div class="square dia-top-right-bottom-left" style="top:350px;left:70px;width:120px;height:120px;"></div>
<x-custom-circle title="Rumah Tinggal" size="small" style="background-color: #234f6c;margin:auto;" />
<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:150px;" />
</div>
<div style="position: absolute; top: 650px; left: 110px;">
<div class="square dia-top-right-bottom-left" style="top:-110px;left:40px;width:200px;height:120px;"></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:260px;width:200px;height:180px;"></div>
<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="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="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>
<x-custom-circle title="DISBUDPAR" size="small" style="background-color: #3a968b;" />
</div>
<div style="position: absolute; top: 280px; left: 550px;">
<div class="square dia-top-left-bottom-right" style="top:-110px;left:-150px;width:200px;height:180px;"></div>
<div class="square dia-top-right-bottom-left" style="top:70px;left:-150px;width:200px;height:130px;"></div>
<x-custom-circle title="Tim Wasdal Gabungan" size="large" style="background-color: #da6635;float:left" />
<div class="square" style="width:650px;height:5px;background-color:black;left:100px;top:75px;"></div>
@component('components.circle', [
'document_title' => 'Kekurangan Potensi',
'document_color' => '#ff5757',
'document_type' => '',
'document_id' => 'chart-lack-of-potential',
'visible_small_circle' => false,
'style' => 'margin-left:180px;top:-20px;'
])
@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" />
</div>
<div style="position: absolute; top: 310px; left: 1150px;">
<div class="square dia-top-left-bottom-right" style="top:90px;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="Tapak Bangunan" size="small" style="background-color: #2390af;float:left;margin-left:20px;" />
</div>
<x-custom-circle title="BPN" size="small" style="background-color: #2390af;position:absolute;left:1270px;top:440px;" />
<div style="position: absolute; top: 470px; left: 430px;">
<div class="square dia-top-right-bottom-left" style="top:-80px;left:20px;width:150px;height:120px;"></div>
<div class="square dia-top-right-bottom-left" style="top:-50px;left:100px;width:100px;height:100px;"></div>
<div class="square dia-top-left-bottom-right" style="top:-50px;left:180px;width:100px;height:100px;"></div>
<div class="square dia-top-left-bottom-right" style="top:-60px;left:240px;width:120px;height:120px;"></div>
<x-custom-circle title="UPT Wasdal" size="small" style="background-color: #0f4853;float:left;" />
<x-custom-circle title="Satpol PP" size="small" style="background-color: #0f4853;float:left;" />
<x-custom-circle title="KEJARI" size="small" style="background-color: #0f4853;float:left;" />
<x-custom-circle title="TNI & POLRI" size="small" style="background-color: #0f4853;float:left;" />
</div>
<x-custom-circle title="UUCK" size="small" style="background-color: #2390af;position:absolute;left:980px;top:500px;" />
<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="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>
@endsection
@section('scripts')
@vite(['resources/js/dashboards/potentials/inside_system.js'])
@endsection

View File

@@ -0,0 +1,73 @@
<!-- base layout -->
@extends('layouts.vertical', ['subtitle' => 'Dashboards'])
<!-- style -->
@section('css')
@vite(['resources/scss/dashboards/potentials/_outside_system.scss'])
@endsection
<!-- content -->
@section('content')
@include('layouts.partials.page-title', ['title' => 'Dashboards', 'subtitle' => 'Luar Sistem'])
<div class="outside-system-wrapper" id="outside-system-wrapper">
<div class="row">
<div class="d-flex justify-content-between align-items-center mt-3 ms-2">
<h2 class="text-danger m-0">
ANALISA BIG DATA MELALUI APLIKASI <br>
SIBEDAS PBG LUAR SISTEM
</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-outside-system" placeholder="Filter Date" />
</div>
</div>
</div>
<div id="outside-system-fixed-container" class="" style="width:1400px;height:770px;position:relative;margin:auto;z-index:1;">
<div style="position: absolute; top: 70px; left: 50px; width: 200px; height: 500px;">
@component('components.circle', [
'document_title' => 'Non Usaha',
'document_color' => '#399787',
'document_type' => 'Berkas',
'document_id' => 'outside-system-non-business',
'visible_small_circle' => true,
'style' => 'top:10px;'
])
@endcomponent
<div class="square dia-top-right-bottom-left" style="top:10px;left:180px;width:230px;height:120px;"></div>
@component('components.circle', [
'document_title' => 'Usaha',
'document_color' => '#5e7c89',
'document_type' => 'Berkas',
'document_id' => 'outside-system-business',
'visible_small_circle' => true,
'style' => 'top:300px;'
])
@endcomponent
<div class="square dia-top-right-bottom-left" style="top:320px;left:170px;width:200px;height:100px;"></div>
<div class="square dia-top-left-bottom-right" style="top:120px;left:180px;width:500px;height:120px;"></div>
<div class="square dia-top-left-bottom-right" style="top:410px;left:180px;width:500px;height:160px;"></div>
</div>
<div style="position: absolute; top: 50px; left: 350px; width: 200px; height: 550px;">
<div class="square" style="width:200px;height:2px;background-color:black;left:100px;top:70px;"></div>
<x-custom-circle title="Keterangan Rencana Kota (KRK)" size="large" style="background-color: #306364;position: absolute;" />
<x-custom-circle title="Keterangan Rencana Kota (KRK)" size="large" style="background-color: #38b64b;position: absolute; top: 320px;" />
<div class="square" style="width:200px;height:2px;background-color:black;left:100px;top:390px;"></div>
</div>
<div style="position: absolute; top: 50px; left: 600px; width: 200px; height: 650px;">
<x-custom-circle title="Samirindu DPMPTSP" size="large" style="background-color: #0e4753;position: absolute; top: 0px;" />
<x-custom-circle title="RAB dan Gambar" size="large" style="background-color: #f0195b;position: absolute; top: 160px;" />
<x-custom-circle title="OSS RBA (Nasional)" size="large" style="background-color: #38b64b;position: absolute; top: 320px;" />
<x-custom-circle title="Dokumen Lingkungan (DLH)" size="large" style="background-color: #393536;position: absolute; top: 480px;" />
<div class="square dia-top-left-bottom-right" style="top:50px;left:120px;width:250px;height:250px;"></div>
<div class="square dia-top-left-bottom-right" style="top:230px;left:120px;width:220px;height:100px;"></div>
<div class="square dia-top-right-bottom-left" style="top:320px;left:120px;width:200px;height:100px;"></div>
<div class="square dia-top-right-bottom-left" style="top:350px;left:120px;width:250px;height:200px;"></div>
</div>
<div style="position: absolute; top: 50px; left: 900px; width: 200px; height: 550px;">
<x-custom-circle title="Pemohon" size="large" style="background-color: #393536;position: absolute; top: 250px;" />
</div>
</div>
</div>
@endsection
<!-- javascripts -->
@section('scripts')
@vite(['resources/js/dashboards/potentials/outside_system.js'])
@endsection

View File

@@ -16,58 +16,49 @@
<ul class="navbar-nav" id="navbar-nav">
<li class="menu-title">Menu</li>
@foreach ($menus as $menu)
<li class="nav-item">
<!-- parent menu -->
@if ($menu->parent_id == null)
<a class="nav-link menu-arrow" href="#sidebar-{{$menu->id}}" data-bs-toggle="collapse" role="button"
aria-expanded="true" aria-controls="sidebar-{{$menu->id}}">
@php
// Fungsi rekursif untuk menampilkan menu bertingkat dengan indentasi
function renderMenu($menus, $depth = 0) {
foreach ($menus as $menu) {
$collapseId = "sidebar-" . $menu->id; // Unique ID untuk Bootstrap Collapse
$hasChildren = $menu->children->count() > 0; // Cek apakah punya anak
$marginLeft = $depth * 5; // Set jarak margin berdasarkan level
echo '<li class="nav-item">';
// Menu utama / anak (dengan dropdown jika punya anak)
echo '<a class="nav-link ' . ($hasChildren ? 'menu-arrow' : '') . '"
href="' . ($hasChildren ? "#$collapseId" : ($menu->url ? (Route::has($menu->url) ? route($menu->url, ['menu_id' => $menu->id]) : $menu->url . '?menu_id=' . $menu->id) : '#')) . '"
' . ($hasChildren ? 'data-bs-toggle="collapse" role="button" aria-expanded="false" aria-controls="' . $collapseId . '"' : '') . '>
<span class="nav-icon">
<iconify-icon icon="{{$menu->icon}}"></iconify-icon>
<iconify-icon icon="' . $menu->icon . '"></iconify-icon>
</span>
<span class="nav-text">{{$menu->name}}</span>
</a>
@endif
<!-- children menu foreach -->
@if ($menu->children->count() > 0)
<div class="collapse" id="sidebar-{{$menu->id}}">
<ul class="nav sub-navbar-nav">
@foreach ( $menu->children as $child)
<li class="sub-nav-item">
<a class="sub-nav-link" href="{{ $child->url ? (Route::has($child->url) ? route($child->url) : $child->url) : '#' }}">
{{ $child->name }}
</a>
</li>
@endforeach
</ul>
</div>
@endif
</li>
@endforeach
<span class="nav-text">' . $menu->name . '</span>
</a>';
// Jika menu punya anak, buat sub-menu
if ($hasChildren) {
echo '<div class="collapse" id="' . $collapseId . '">
<ul class="nav sub-navbar-nav">';
renderMenu($menu->children, $depth + 1); // Rekursi dengan level lebih dalam
echo '</ul></div>';
}
echo '</li>';
}
}
@endphp
@php
// Tampilkan hanya menu dengan parent_id NULL (menu utama)
renderMenu($menus->where('parent_id', null));
@endphp
</ul>
</div>
</div>
<!-- Efek Bintang -->
<div class="animated-stars">
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
<div class="shooting-star"></div>
@for ($i = 0; $i < 20; $i++)
<div class="shooting-star"></div>
@endfor
</div>

View File

@@ -3,32 +3,64 @@
<style>
/* .floating-icon {
position: fixed;
right: 30px;
bottom: 70px;
background: white;
padding: 10px;
border-radius: 50%;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
cursor: pointer;
z-index: 1000;
} */
.floating-icon {
position: fixed;
right: 40px;
bottom: 100px;
width: 70px; /* Sesuaikan ukuran */
height: 70px; /* Sesuaikan ukuran */
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'); /* Path ke gambar */
background-size: cover; /* Agar gambar menyesuaikan */
background-position: center; /* Memusatkan gambar */
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;
}
.menu-arrow {
transition: transform 0.3s ease;
}
.nav-link[aria-expanded="true"] .menu-arrow i {
transform: rotate(180deg);
}
</style>
<head>
@@ -46,22 +78,10 @@
@include('layouts.partials/topbar')
<div class="page-content">
{{-- <div class="container-fluid">
@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'))
@if (!Request::is('chatbot') && !Request::is('main-chatbot'))
<a href="{{ route('chatbot.index') }}" class="floating-icon">
</a>

Some files were not shown because too many files have changed in this diff Show More