Compare commits

...

23 Commits

Author SHA1 Message Date
arifal
091b1f305e fix handle retry button 2025-03-25 15:54:02 +07:00
arifal
e7950e22f2 add flatpicker for edit pbg task data 2025-03-24 21:16:05 +07:00
arifal
b68641db03 backup local db and add global setting seeder to run default 2025-03-24 11:08:10 +07:00
arifal
fefb85ac7a remove get sample data from service 2025-03-21 19:10:17 +07:00
arifal
d28a08a24c fix add bigdata resume after syncronize 2025-03-21 19:08:18 +07:00
arifal
654d2efe19 fix syncronize using worker and add duration syncronize 2025-03-21 18:44:28 +07:00
arifal
0a080763cd try catch handle refresh token fail to login again 2025-03-21 16:03:10 +07:00
arifal
f3ef21d1be fix style error message when failed login 2025-03-21 15:10:28 +07:00
arifal
d7bff86741 fix menu with parent data and remove action google sheet not available feature 2025-03-21 14:54:18 +07:00
arifal
f36f250700 fix supervisorctl syntax on deploy 2025-03-20 20:19:38 +07:00
arifal
2c5da87856 fix reload php service on deploy 2025-03-20 20:18:09 +07:00
arifal
a195559c4b fix running syntax seeder on automation deploy syntax 2025-03-20 20:15:30 +07:00
arifal
088f173fec fix add params filter date on grid js, export excel and pdf payment recaps 2025-03-20 20:12:42 +07:00
arifal
eadfddb3a4 add export excel and pdf district payment recaps 2025-03-19 18:55:51 +07:00
arifal
47a9fb1dfb add export excel and pdf report director 2025-03-19 18:22:00 +07:00
arifal
1713e32b67 add export pdf report tourisms and report ptsp 2025-03-19 17:15:10 +07:00
arifal
cf998455e0 add export excel report tourisms 2025-03-19 16:16:10 +07:00
arifal
5e1c9f3a2e fix dashboard pbg, add soft delete users, fix js create pbg task 2025-03-19 14:06:10 +07:00
arifal
e940b8d6c7 fix handle sync using button 2025-03-18 08:10:06 +07:00
arifal
5e139bc29c change command scraping 2025-03-18 06:47:53 +07:00
arifal
f9e1aa1604 fix service scraping data 2025-03-18 06:42:42 +07:00
arifal
2e385f80cd fix optimize syncronize 2025-03-14 19:10:28 +07:00
arifal
e2c26e0eff fix reklame route index 2025-03-13 16:23:17 +07:00
74 changed files with 3939 additions and 1255 deletions

View File

@@ -17,10 +17,10 @@ sudo nano /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker] [program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d 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 command=php /home/arifal/development/sibedas-pbg-web/artisan queue:work --queue=default --timeout=40000 --tries=1
autostart=true autostart=true
autorestart=true autorestart=true
numprocs=4 numprocs=1
redirect_stderr=true redirect_stderr=true
stdout_logfile=/home/arifal/development/sibedas-pbg-web/storage/logs/worker.log stdout_logfile=/home/arifal/development/sibedas-pbg-web/storage/logs/worker.log
stopasgroup=true stopasgroup=true

View File

@@ -0,0 +1,95 @@
<?php
namespace App\Console\Commands;
use App\Jobs\ScrapingDataJob;
use App\Models\ImportDatasource;
use App\Services\ServiceGoogleSheet;
use App\Services\ServicePbgTask;
use App\Services\ServiceTabPbgTask;
use App\Services\ServiceTokenSIMBG;
use GuzzleHttp\Client; // Import Guzzle Client
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
class ScrapingData extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:scraping-data';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Inject dependencies.
*/
public function __construct(
) {
parent::__construct();
}
public function handle()
{
dispatch(new ScrapingDataJob());
$this->info("Scraping job dispatched successfully");
}
/**
* Execute the console command.
*/
// public function handle()
// {
// try {
// // Create a record with "processing" status
// $import_datasource = ImportDatasource::create([
// 'message' => 'Initiating scraping...',
// 'response_body' => null,
// 'status' => 'processing',
// 'start_time' => now()
// ]);
// // Run the service
// $service_google_sheet = new ServiceGoogleSheet();
// $service_google_sheet->run_service();
// // Run the ServicePbgTask with injected Guzzle Client
// $this->service_pbg_task->run_service();
// // run the service pbg task assignments
// $this->service_tab_pbg_task->run_service();
// // Update the record status to "success" after completion
// $import_datasource->update([
// 'status' => 'success',
// 'message' => 'Scraping completed successfully.',
// 'finish_time' => now()
// ]);
// } catch (\Exception $e) {
// // Log the error for debugging
// Log::error('Scraping failed: ' . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
// // Handle errors by updating the status to "failed"
// if (isset($import_datasource)) {
// $import_datasource->update([
// 'status' => 'failed',
// 'response_body' => 'Error: ' . $e->getMessage(),
// 'finish_time' => now()
// ]);
// }
// }
// }
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Enums;
enum PbgTaskApplicationTypes: string
{
case PERSETUJUAN_BG = '1';
case PERUBAHAN_BG = '2';
case SLF_BB = '4';
case SLF = '5';
public static function labels(): array
{
return [
null => "Pilih Application Type",
self::PERSETUJUAN_BG->value => 'Persetujuan Bangunan Gedung',
self::PERUBAHAN_BG->value => 'Perubahan Bangunan Gedung',
self::SLF_BB->value => 'Sertifikat Laik Fungsi - Bangunan Baru',
self::SLF->value => 'Sertifikat Laik Fungsi',
];
}
public static function getLabel(?string $status): ?string
{
return self::labels()[$status] ?? null;
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Enums;
enum PbgTaskStatus: int
{
case VERIFIKASI_KELENGKAPAN = 1;
case PERBAIKAN_DOKUMEN = 2;
case PERMOHONAN_DIBATALKAN = 3;
case MENUNGGU_PENUGASAN_TPT_TPA = 4;
case MENUNGGU_JADWAL_KONSULTASI = 5;
case PELAKSANAAN_KONSULTASI = 6;
case PERBAIKAN_DOKUMEN_KONSULTASI = 8;
case PERMOHONAN_DITOLAK = 9;
case PERHITUNGAN_RETRIBUSI = 10;
case MENUNGGU_PEMBAYARAN_RETRIBUSI = 14;
case VERIFIKASI_PEMBAYARAN_RETRIBUSI = 15;
case RETRIBUSI_TIDAK_SESUAI = 16;
case VERIFIKASI_SK_PBG = 18;
case PENERBITAN_SK_PBG = 19;
case SK_PBG_TERBIT = 20;
case PENERBITAN_SPPST = 24;
case PROSES_PENERBITAN_SKRD = 25;
case MENUNGGU_PENUGASAN_TPT = 26;
case VERIFIKASI_DATA_TPT = 27;
case SERTIFIKAT_SLF_TERBIT = 28;
public static function getStatuses(): array
{
return [
null => "Pilih Status",
self::VERIFIKASI_KELENGKAPAN->value => "Verifikasi Kelengkapan Dokumen",
self::PERBAIKAN_DOKUMEN->value => "Perbaikan Dokumen",
self::PERMOHONAN_DIBATALKAN->value => "Permohonan Dibatalkan",
self::MENUNGGU_PENUGASAN_TPT_TPA->value => "Menunggu Penugasan TPT/TPA",
self::MENUNGGU_JADWAL_KONSULTASI->value => "Menunggu Jadwal Konsultasi",
self::PELAKSANAAN_KONSULTASI->value => "Pelaksanaan Konsultasi",
self::PERBAIKAN_DOKUMEN_KONSULTASI->value => "Perbaikan Dokumen Konsultasi",
self::PERMOHONAN_DITOLAK->value => "Permohonan Ditolak",
self::PERHITUNGAN_RETRIBUSI->value => "Perhitungan Retribusi",
self::MENUNGGU_PEMBAYARAN_RETRIBUSI->value => "Menunggu Pembayaran Retribusi",
self::VERIFIKASI_PEMBAYARAN_RETRIBUSI->value => "Verifikasi Pembayaran Retribusi",
self::RETRIBUSI_TIDAK_SESUAI->value => "Retribusi Tidak Sesuai",
self::VERIFIKASI_SK_PBG->value => "Verifikasi SK PBG",
self::PENERBITAN_SK_PBG->value => "Penerbitan SK PBG",
self::SK_PBG_TERBIT->value => "SK PBG Terbit",
self::PENERBITAN_SPPST->value => "Penerbitan SPPST",
self::PROSES_PENERBITAN_SKRD->value => "Proses Penerbitan SKRD",
self::MENUNGGU_PENUGASAN_TPT->value => "Menunggu Penugasan TPT",
self::VERIFIKASI_DATA_TPT->value => "Verifikasi Data TPT",
self::SERTIFIKAT_SLF_TERBIT->value => "Sertifikat SLF Terbit",
];
}
public static function getLabel(?int $status): ?string
{
return self::getStatuses()[$status] ?? null;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Exports;
use App\Models\PbgTaskGoogleSheet;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
class DistrictPaymentRecapExport implements FromCollection, WithHeadings
{
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
return PbgTaskGoogleSheet::select(
'kecamatan',
DB::raw('SUM(nilai_retribusi_keseluruhan_simbg) as total')
)
->groupBy('kecamatan')->get();
}
public function headings(): array{
return [
'Kecamatan',
'Total'
];
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Exports;
use App\Models\BigdataResume;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
class ReportDirectorExport implements FromCollection, WithHeadings, WithMapping
{
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
return BigdataResume::select(
'potention_count',
'potention_sum',
'non_verified_count',
'non_verified_sum',
'verified_count',
'verified_sum',
'business_count',
'business_sum',
'non_business_count',
'non_business_sum',
'spatial_count',
'spatial_sum',
'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',
'year',
'created_at'
)->orderBy('id', 'desc')->get();
}
public function headings(): array{
return [
"Jumlah Potensi" ,
"Total Potensi" ,
"Jumlah Berkas Belum Terverifikasi" ,
"Total Berkas Belum Terverifikasi" ,
"Jumlah Berkas Terverifikasi" ,
"Total Berkas Terverifikasi" ,
"Jumlah Usaha" ,
"Total Usaha" ,
"Jumlah Non Usaha" ,
"Total Non Usaha" ,
"Jumlah Tata Ruang" ,
"Total Tata Ruang" ,
"Jumlah Menunggu Klik DPMPTSP" ,
"Total Menunggu Klik DPMPTSP" ,
"Jumlah Realisasi Terbit PBG" ,
"Total Realisasi Terbit PBG" ,
"Jumlah Proses Dinas Teknis" ,
"Total Proses Dinas Teknis",
"Tahun",
"Created"
];
}
public function map($row): array
{
return [
$row->potention_count,
$row->potention_sum,
$row->non_verified_count,
$row->non_verified_sum,
$row->verified_count,
$row->verified_sum,
$row->business_count,
$row->business_sum,
$row->non_business_count,
$row->non_business_sum,
$row->spatial_count,
$row->spatial_sum,
$row->waiting_click_dpmptsp_count,
$row->waiting_click_dpmptsp_sum,
$row->issuance_realization_pbg_count,
$row->issuance_realization_pbg_sum,
$row->process_in_technical_office_count,
$row->process_in_technical_office_sum,
$row->year,
$row->created_at ? $row->created_at->format('Y-m-d H:i:s') : null, // Format created_at as Y-m-d
];
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Exports;
use App\Models\BigdataResume;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
class ReportPaymentRecapExport implements FromCollection, WithHeadings
{
/**
* @return \Illuminate\Support\Collection
*/
protected $startDate;
protected $endDate;
public function __construct($startDate, $endDate){
$this->startDate = $startDate;
$this->endDate = $endDate;
}
public function collection()
{
$query = BigdataResume::query()->orderBy('id', 'desc');
if ($this->startDate && $this->endDate) {
$query->whereBetween('created_at', [$this->startDate, $this->endDate]);
}
$items = $query->get();
$categoryMap = [
'potention_sum' => 'Potensi',
'non_verified_sum' => 'Belum Terverifikasi',
'verified_sum' => 'Terverifikasi',
'business_sum' => 'Usaha',
'non_business_sum' => 'Non Usaha',
'spatial_sum' => 'Tata Ruang',
'waiting_click_dpmptsp_sum' => 'Menunggu Klik DPMPTSP',
'issuance_realization_pbg_sum' => 'Realisasi Terbit PBG',
'process_in_technical_office_sum' => 'Proses Di Dinas Teknis',
];
// Restructure response
$data = [];
foreach ($items as $item) {
$createdAt = $item->created_at;
$id = $item->id;
foreach ($item->toArray() as $key => $value) {
// Only include columns with "sum" in their names
if (strpos($key, 'sum') !== false) {
$data[] = [
'category' => $categoryMap[$key] ?? $key, // Map category
'nominal' => number_format($value, 0, ',', '.'), // Format number
'created_at' => $createdAt->format('Y-m-d H:i:s'), // Format date
];
}
}
}
return collect($data);
}
public function headings(): array{
return [
'Kategori',
'Nominal',
'Created'
];
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Exports;
use App\Models\PbgTask;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
class ReportPbgPtspExport implements FromCollection, WithHeadings
{
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
return PbgTask::select(
'status_name',
DB::raw('COUNT(*) as total')
)
->groupBy('status', 'status_name')
->get();
}
public function headings(): array
{
return [
'Status Name',
'Total'
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Exports;
use App\Models\TourismBasedKBLI;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
class ReportTourismExport implements FromCollection, WithHeadings
{
/**
* @return \Illuminate\Support\Collection
*/
public function collection()
{
return TourismBasedKBLI::select('kbli_title', 'total_records')->get();
}
public function headings(): array{
return [
'Jenis Bisnis Pariwisata',
'Jumlah Total'
];
}
}

View File

@@ -2,12 +2,17 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Exports\ReportDirectorExport;
use App\Exports\ReportPaymentRecapExport;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Resources\BigdataResumeResource; use App\Http\Resources\BigdataResumeResource;
use App\Models\BigdataResume; use App\Models\BigdataResume;
use App\Models\DataSetting; use App\Models\DataSetting;
use Barryvdh\DomPDF\Facade\Pdf;
use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Facades\Excel;
class BigDataResumeController extends Controller class BigDataResumeController extends Controller
{ {
@@ -178,11 +183,14 @@ class BigDataResumeController extends Controller
try { try {
$query = BigdataResume::query()->orderBy('id', 'desc'); $query = BigdataResume::query()->orderBy('id', 'desc');
if ($request->filled('date')) { if ($request->filled('start_date') && $request->filled('end_date')) {
$query->where('year', 'LIKE', '%' . $request->input('search') . '%'); $startDate = Carbon::parse($request->input('start_date'))->startOfDay();
$endDate = Carbon::parse($request->input('end_date'))->endOfDay();
$query->whereBetween('created_at', [$startDate, $endDate]);
} }
$data = $query->paginate(10); $data = $query->paginate(50);
// Restructure response // Restructure response
$transformedData = []; $transformedData = [];
@@ -207,7 +215,7 @@ class BigDataResumeController extends Controller
return response()->json([ return response()->json([
'data' => $transformedData, // Flat array 'data' => $transformedData, // Flat array
'pagination' => [ 'pagination' => [
'total' => $data->total(), 'total' => count($transformedData),
'per_page' => $data->perPage(), 'per_page' => $data->perPage(),
'current_page' => $data->currentPage(), 'current_page' => $data->currentPage(),
'last_page' => $data->lastPage(), 'last_page' => $data->lastPage(),
@@ -219,7 +227,99 @@ class BigDataResumeController extends Controller
} }
} }
public function export_excel_payment_recaps(Request $request)
{
$startDate = null;
$endDate = null;
if ($request->filled('start_date') && $request->filled('end_date')) {
$startDate = Carbon::parse($request->input('start_date'))->startOfDay();
$endDate = Carbon::parse($request->input('end_date'))->endOfDay();
}
return Excel::download(new ReportPaymentRecapExport($startDate, $endDate), 'laporan-rekap-pembayaran.xlsx');
}
public function export_pdf_payment_recaps(Request $request){
$query = BigdataResume::query()->orderBy('id', 'desc');
if ($request->filled('start_date') && $request->filled('end_date')) {
$startDate = Carbon::parse($request->input('start_date'))->startOfDay();
$endDate = Carbon::parse($request->input('end_date'))->endOfDay();
$query->whereBetween('created_at', [$startDate, $endDate]);
}
$items = $query->get();
// Define category mapping
$categoryMap = [
'potention_sum' => 'Potensi',
'non_verified_sum' => 'Belum Terverifikasi',
'verified_sum' => 'Terverifikasi',
'business_sum' => 'Usaha',
'non_business_sum' => 'Non Usaha',
'spatial_sum' => 'Tata Ruang',
'waiting_click_dpmptsp_sum' => 'Menunggu Klik DPMPTSP',
'issuance_realization_pbg_sum' => 'Realisasi Terbit PBG',
'process_in_technical_office_sum' => 'Proses Di Dinas Teknis',
];
// Restructure response
$data = [];
foreach ($items as $item) {
$createdAt = $item->created_at;
$id = $item->id;
foreach ($item->toArray() as $key => $value) {
// Only include columns with "sum" in their names
if (strpos($key, 'sum') !== false) {
$data[] = [
'id' => $id,
'category' => $categoryMap[$key] ?? $key, // Map category
'nominal' => $value, // Format number
'created_at' => $createdAt->format('Y-m-d H:i:s'), // Format date
];
}
}
}
$pdf = Pdf::loadView('exports.payment_recaps_report', compact('data'));
return $pdf->download('laporan-rekap-pembayaran.pdf');
}
public function export_excel_report_director(){
return Excel::download(new ReportDirectorExport, 'laporan-pimpinan.xlsx');
}
public function export_pdf_report_director(){
$data = BigdataResume::select(
'potention_count',
'potention_sum',
'non_verified_count',
'non_verified_sum',
'verified_count',
'verified_sum',
'business_count',
'business_sum',
'non_business_count',
'non_business_sum',
'spatial_count',
'spatial_sum',
'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',
'year',
'created_at'
)->orderBy('id', 'desc')->get();
$pdf = Pdf::loadView('exports.director_report', compact('data'))->setPaper('a4', 'landscape');
return $pdf->download('laporan-pimpinan.pdf');
}
private function response_empty_resume(){ private function response_empty_resume(){
$result = [ $result = [
'target_pad' => [ 'target_pad' => [

View File

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

View File

@@ -3,6 +3,8 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Enums\ImportDatasourceStatus; use App\Enums\ImportDatasourceStatus;
use App\Enums\PbgTaskApplicationTypes;
use App\Enums\PbgTaskStatus;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\PbgTaskMultiStepRequest; use App\Http\Requests\PbgTaskMultiStepRequest;
use App\Http\Resources\PbgTaskResource; use App\Http\Resources\PbgTaskResource;
@@ -14,6 +16,7 @@ use App\Services\GoogleSheetService;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rules\Enum;
class PbgTaskController extends Controller class PbgTaskController extends Controller
{ {
@@ -116,9 +119,63 @@ class PbgTaskController extends Controller
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
*/ */
public function update(Request $request, string $id) public function update(Request $request, string $task_uuid)
{ {
// try{
$pbg_task = PbgTask::where('uuid',$task_uuid)->first();
if(!$pbg_task){
return response()->json([
"success"=> false,
"message"=> "Data PBG Task tidak ditemukan",
], 404);
}
$validated = $request->validate([
'name' => 'required|string|max:255',
'owner_name' => 'required|string|max:255',
'application_type' => ['nullable', new Enum(PbgTaskApplicationTypes::class)],
'condition' => 'required|string|max:255',
'registration_number' => 'required|string|max:255',
'document_number' => 'required|string|max:255',
'status' => ['nullable', new Enum(PbgTaskStatus::class)],
'address' => 'required|string|max:255',
'slf_status_name' => 'nullable|string|max:255',
'function_type' => 'required|string|max:255',
'consultation_type' => 'required|string|max:255',
'due_date' => 'nullable|date|after_or_equal:today',
]);
$statusLabel = $validated['status'] !== null ? PbgTaskStatus::getLabel($validated['status']) : null;
$applicationLabel = $validated['application_type'] !== null ? PbgTaskApplicationTypes::getLabel($validated['application_type']) : null;
$pbg_task->update([
'name' => $validated['name'],
'owner_name' => $validated['owner_name'],
'application_type' => $validated['application_type'],
'application_type_name' => $applicationLabel, // Automatically set application_type_name
'condition' => $validated['condition'],
'registration_number' => $validated['registration_number'],
'document_number' => $validated['document_number'],
'status' => $validated['status'],
'status_name' => $statusLabel, // Automatically set status_name
'address' => $validated['address'],
'slf_status_name' => $validated['slf_status_name'],
'function_type' => $validated['function_type'],
'consultation_type' => $validated['consultation_type'],
'due_date' => $validated['due_date'],
]);
return response()->json([
"success"=> true,
"message"=> "Data berhasil diubah",
"data"=> $pbg_task
]);
}catch(\Exception $e){
return response()->json([
"success"=> false,
"message"=> $e->getMessage(),
]);
}
} }
/** /**
@@ -135,255 +192,4 @@ class PbgTaskController extends Controller
]); ]);
} }
public function syncPbgFromGoogleSheet(){
$import_datasource = ImportDatasource::create([
"message" => "initialization",
"response_body" => null,
"status" => ImportDatasourceStatus::Processing->value,
]);
try{
$totalRowCount = $this->googleSheetService->getLastRowByColumn("C");
$sheetData = $this->googleSheetService->getSheetDataCollection($totalRowCount);
$sheet_big_data = $this->googleSheetService->get_data_by_sheet();
$data_setting_result = []; // Initialize result storage
$found_section = null; // Track which section is found
foreach ($sheet_big_data as $row) {
// Check for section headers
if (in_array("•PROSES PENERBITAN:", $row)) {
$found_section = "MENUNGGU_KLIK_DPMPTSP";
} elseif (in_array("•BERKAS AKTUAL TERVERIFIKASI DINAS TEKNIS 2024:", $row)) {
$found_section = "REALISASI_TERBIT_PBG";
} elseif (in_array("•TERPROSES DI DPUTR: belum selesai rekomtek'", $row)) {
$found_section = "PROSES_DINAS_TEKNIS";
}
// If a section is found and we reach "Grand Total", save the corresponding values
if ($found_section && isset($row[0]) && trim($row[0]) === "Grand Total") {
if ($found_section === "MENUNGGU_KLIK_DPMPTSP") {
$data_setting_result["MENUNGGU_KLIK_DPMPTSP_COUNT"] = $row[2] ?? null;
$data_setting_result["MENUNGGU_KLIK_DPMPTSP_SUM"] = $row[3] ?? null;
} elseif ($found_section === "REALISASI_TERBIT_PBG") {
$data_setting_result["REALISASI_TERBIT_PBG_COUNT"] = $row[2] ?? null;
$data_setting_result["REALISASI_TERBIT_PBG_SUM"] = $row[4] ?? null;
} elseif ($found_section === "PROSES_DINAS_TEKNIS") {
$data_setting_result["PROSES_DINAS_TEKNIS_COUNT"] = $row[2] ?? null;
$data_setting_result["PROSES_DINAS_TEKNIS_SUM"] = $row[3] ?? null;
}
// Reset section tracking after capturing "Grand Total"
$found_section = null;
}
}
foreach ($data_setting_result as $key => $value) {
DataSetting::updateOrInsert(
["key" => $key], // Find by key
["value" => $value] // Update or insert value
);
}
$mapToUpsert = [];
$count = 0;
foreach($sheetData as $data){
$mapToUpsert[] =
[
'no_registrasi' => $data['no__registrasi'] ?? null,
'jenis_konsultasi' => $data['jenis_konsultasi'] ?? null,
'fungsi_bg' => $data['fungsi_bg'] ?? null,
'tgl_permohonan' => $this->convertToDate($data['tgl_permohonan']),
'status_verifikasi' => $data['status_verifikasi'] ?? null,
'status_permohonan' => $this->convertToDate($data['status_permohonan']),
'alamat_pemilik' => $data['alamat_pemilik'] ?? null,
'no_hp' => $data['no__hp'] ?? null,
'email' => $data['e_mail'] ?? null,
'tanggal_catatan' => $this->convertToDate($data['tanggal_catatan']),
'catatan_kekurangan_dokumen' => $data['catatan_kekurangan_dokumen'] ?? null,
'gambar' => $data['gambar'] ?? null,
'krk_kkpr' => $data['krk_kkpr'] ?? null,
'no_krk' => $data['no__krk'] ?? null,
'lh' => $data['lh'] ?? null,
'ska' => $data['ska'] ?? null,
'keterangan' => $data['keterangan'] ?? null,
'helpdesk' => $data['helpdesk'] ?? null,
'pj' => $data['pj'] ?? null,
'kepemilikan' => $data['kepemilikan'] ?? null,
'potensi_taru' => $data['potensi_taru'] ?? null,
'validasi_dinas' => $data['validasi_dinas'] ?? null,
'kategori_retribusi' => $data['kategori_retribusi'] ?? null,
'no_urut_ba_tpt' => $data['no__urut_ba_tpt__2024_0001_'] ?? null,
'tanggal_ba_tpt' => $this->convertToDate($data['tanggal_ba_tpt']),
'no_urut_ba_tpa' => $data['no__urut_ba_tpa'] ?? null,
'tanggal_ba_tpa' => $this->convertToDate($data['tanggal_ba_tpa']),
'no_urut_skrd' => $data['no__urut_skrd__2024_0001_'] ?? null,
'tanggal_skrd' => $this->convertToDate($data['tanggal_skrd']),
'ptsp' => $data['ptsp'] ?? null,
'selesai_terbit' => $data['selesai_terbit'] ?? null,
'tanggal_pembayaran' => $this->convertToDate($data['tanggal_pembayaran__yyyy_mm_dd_']),
'format_sts' => $data['format_sts'] ?? null,
'tahun_terbit' => (int) $data['tahun_terbit'] ?? null,
'tahun_berjalan' => (int) $data['tahun_berjalan'] ?? null,
'kelurahan' => $data['kelurahan'] ?? null,
'kecamatan' => $data['kecamatan'] ?? null,
'lb' => $this->convertToDecimal($data['lb']) ?? null,
'tb' => $this->convertToDecimal($data['tb']) ?? null,
'jlb' => (int) $data['jlb'] ?? null,
'unit' => (int) $data['unit'] ?? null,
'usulan_retribusi' => (int) $data['usulan_retribusi'] ?? null,
'nilai_retribusi_keseluruhan_simbg' => $this->convertToDecimal($data['nilai_retribusi_keseluruhan__simbg_']) ?? null,
'nilai_retribusi_keseluruhan_pad' => $this->convertToDecimal($data['nilai_retribusi_keseluruhan__pad_']) ?? null,
'denda' => $this->convertToDecimal($data['denda']) ?? null,
'latitude' => $data['latitude'] ?? null,
'longitude' => $data['longitude'] ?? null,
'nik_nib' => $data['nik_nib'] ?? null,
'dok_tanah' => $data['dok__tanah'] ?? null,
'temuan' => $data['temuan'] ?? null,
];
}
DB::beginTransaction();
$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',
]);
}
$total_data = count($mapToUpsert);
$import_datasource->update([
"message" => "Successfully processed: {$total_data}",
"status" => ImportDatasourceStatus::Success->value,
]);
DB::commit();
return response()->json([
"success" => true,
"message" => "Data berhasil disimpan ke database"
], 200);
}catch(\Exception $ex){
DB::rollBack();
$import_datasource->update([
"message" => "Failed to importing",
"response_body" => $ex->getMessage(),
"status" => ImportDatasourceStatus::Failed->value,
]);
return response()->json([
"success" => false,
"message" => "Gagal menyimpan data",
"error" => $ex->getMessage()
], 500);
}
}
protected function convertToDecimal(?string $value): ?float
{
if (empty($value)) {
return null; // Return null if the input is empty
}
// Remove all non-numeric characters except comma and dot
$value = preg_replace('/[^0-9,\.]/', '', $value);
// If the number contains both dot (.) and comma (,)
if (strpos($value, '.') !== false && strpos($value, ',') !== false) {
$value = str_replace('.', '', $value); // Remove thousands separator
$value = str_replace(',', '.', $value); // Convert decimal separator to dot
}
// If only a dot is present (assumed as thousands separator)
elseif (strpos($value, '.') !== false) {
$value = str_replace('.', '', $value); // Remove all dots (treat as thousands separators)
}
// If only a comma is present (assumed as decimal separator)
elseif (strpos($value, ',') !== false) {
$value = str_replace(',', '.', $value); // Convert comma to dot (decimal separator)
}
// Ensure the value is numeric before returning
return is_numeric($value) ? (float) number_format((float) $value, 2, '.', '') : null;
}
protected function convertToInteger($value) {
// Check if the value is an empty string, and return null if true
if (trim($value) === "") {
return null;
}
// Otherwise, cast to integer
return (int) $value;
}
protected function convertToDate($dateString)
{
try {
// Check if the string is empty
if (empty($dateString)) {
return null;
}
// Try to parse the date string
$date = Carbon::parse($dateString);
// Return the Carbon instance
return $date->format('Y-m-d');
} catch (\Exception $e) {
// Return null if an error occurs during parsing
return null;
}
}
} }

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Api;
use App\Exports\ReportPbgPtspExport;
use App\Http\Controllers\Controller;
use App\Models\PbgTask;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
class ReportPbgPtspController extends Controller
{
public function export_excel(){
return Excel::download(new ReportPbgPtspExport, 'laporan-ptsp.xlsx');
}
public function export_pdf(){
$data = PbgTask::select(
'status',
'status_name', // Keeping this column
DB::raw('COUNT(*) as total')
)
->groupBy('status', 'status_name')
->get();
$pdf = Pdf::loadView('exports.ptsp_report', compact('data'));
return $pdf->download('laporan-ptsp.pdf');
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Api;
use App\Exports\ReportTourismExport;
use App\Http\Controllers\Controller;
use App\Models\TourismBasedKBLI;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
class ReportTourismsController extends Controller
{
public function export_excel(){
return Excel::download(new ReportTourismExport, 'laporan-pariwisata.xlsx');
}
public function export_pdf(){
$data = TourismBasedKBLI::all();
$pdf = Pdf::loadView('exports.tourisms_report', compact('data'));
return $pdf->download('laporan-pariwisata.pdf');
}
}

View File

@@ -2,14 +2,17 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Exports\DistrictPaymentRecapExport;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Resources\RequestAssignmentResouce; use App\Http\Resources\RequestAssignmentResouce;
use App\Models\PbgTask; use App\Models\PbgTask;
use App\Models\PbgTaskGoogleSheet; use App\Models\PbgTaskGoogleSheet;
use Barryvdh\DomPDF\Facade\Pdf;
use DB; use DB;
use Exception; use Exception;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Log; use Log;
use Maatwebsite\Excel\Facades\Excel;
class RequestAssignmentController extends Controller class RequestAssignmentController extends Controller
{ {
@@ -49,6 +52,19 @@ class RequestAssignmentController extends Controller
return response()->json(['message' => 'Terjadi kesalahan: ' . $e->getMessage()], 500); return response()->json(['message' => 'Terjadi kesalahan: ' . $e->getMessage()], 500);
} }
} }
public function export_excel_district_payment_recaps(){
return Excel::download(new DistrictPaymentRecapExport, 'laporan-rekap-data-pembayaran.xlsx');
}
public function export_pdf_district_payment_recaps(){
$data = PbgTaskGoogleSheet::select(
'kecamatan',
DB::raw('SUM(nilai_retribusi_keseluruhan_simbg) as total')
)
->groupBy('kecamatan')->get();
$pdf = Pdf::loadView('exports.district_payment_report', compact('data'));
return $pdf->download('laporan-rekap-data-pembayaran.pdf');
}
public function report_pbg_ptsp() public function report_pbg_ptsp()
{ {
try { try {

View File

@@ -4,9 +4,16 @@ namespace App\Http\Controllers\Api;
use App\Enums\ImportDatasourceStatus; use App\Enums\ImportDatasourceStatus;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Jobs\RetrySyncronizeJob;
use App\Jobs\ScrapingDataJob;
use App\Jobs\SyncronizeSIMBG; use App\Jobs\SyncronizeSIMBG;
use App\Models\ImportDatasource; use App\Models\ImportDatasource;
use App\Traits\GlobalApiResponse; use App\Traits\GlobalApiResponse;
use App\Services\ServiceTokenSIMBG;
use GuzzleHttp\Client;
use App\Services\ServiceGoogleSheet;
use App\Services\ServicePbgTask;
use App\Services\ServiceTabPbgTask;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -23,40 +30,33 @@ class ScrapingController extends Controller
return $this->resError("Failed to execute while processing another scraping"); return $this->resError("Failed to execute while processing another scraping");
} }
// run service artisan command // use ole schema synchronization
SyncronizeSIMBG::dispatch(); // dispatch(new SyncronizeSIMBG());
// use new schema synchronization
dispatch(new ScrapingDataJob());
return $this->resSuccess(["message" => "Success execute scraping service on background, check status for more"]); return $this->resSuccess(["message" => "Success execute scraping service on background, check status for more"]);
} }
/** public function retry_syncjob(string $import_datasource_id){
* Store a newly created resource in storage. try{
*/
public function store(Request $request) $import_datasource = ImportDatasource::find($import_datasource_id);
{ if(!$import_datasource){
// return $this->resError("Invalid import datasource id", null, 404);
} }
/** dispatch(new RetrySyncronizeJob($import_datasource->id));
* Display the specified resource. return response()->json([
*/ "success" => true,
public function show(string $id) "message" => "Retrying scrape job on background, check status for more"
{ ]);
// }catch(\Exception $e){
} return response()->json([
"success" => false,
/** "message" => "Failed to retry sync job",
* Update the specified resource in storage. "error" => $e->getMessage()
*/ ]);
public function update(Request $request, string $id) }
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
} }
} }

View File

@@ -85,4 +85,17 @@ class UsersController extends Controller
return response()->json(['message' => $e->getMessage()],500); return response()->json(['message' => $e->getMessage()],500);
} }
} }
public function destroy($id){
try{
$user = User::findOrFail($id);
DB::beginTransaction();
$user->delete();
DB::commit();
return response()->json(['message' => 'Successfully deleted'], 200);
}catch(\Exception $e){
Log::error('Failed to delete user: '. $e->getMessage());
return response()->json(['message' => 'Failed to delete user'],500);
}
}
} }

View File

@@ -15,7 +15,6 @@ class ReportTourismController extends Controller
public function index() public function index()
{ {
$tourismBasedKBLI = TourismBasedKBLI::all(); $tourismBasedKBLI = TourismBasedKBLI::all();
info($tourismBasedKBLI);
return view('report.tourisms.index', compact('tourismBasedKBLI')); return view('report.tourisms.index', compact('tourismBasedKBLI'));
} }
} }

View File

@@ -2,11 +2,13 @@
namespace App\Http\Controllers\RequestAssignment; namespace App\Http\Controllers\RequestAssignment;
use App\Enums\PbgTaskApplicationTypes;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\PbgTask; use App\Models\PbgTask;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use App\Enums\PbgTaskStatus;
class PbgTaskController extends Controller class PbgTaskController extends Controller
{ {
@@ -60,7 +62,9 @@ class PbgTaskController extends Controller
public function show(string $id) public function show(string $id)
{ {
$data = PbgTask::with(['pbg_task_retributions','pbg_task_index_integrations', 'pbg_task_retributions.pbg_task_prasarana'])->findOrFail($id); $data = PbgTask::with(['pbg_task_retributions','pbg_task_index_integrations', 'pbg_task_retributions.pbg_task_prasarana'])->findOrFail($id);
return view("pbg_task.show", compact("data")); $statusOptions = PbgTaskStatus::getStatuses();
$applicationTypes = PbgTaskApplicationTypes::labels();
return view("pbg_task.show", compact("data", 'statusOptions', 'applicationTypes'));
} }
/** /**

View File

@@ -2,6 +2,7 @@
namespace App\Http\Resources; namespace App\Http\Resources;
use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
@@ -14,13 +15,21 @@ class ImportDatasourceResource extends JsonResource
*/ */
public function toArray(Request $request): array public function toArray(Request $request): array
{ {
$startTime = $this->start_time ? Carbon::parse($this->start_time) : null;
$finishTime = $this->finish_time ? Carbon::parse($this->finish_time) : null;
return [ return [
"id"=> $this->id, "id"=> $this->id,
"message" => $this->message, "message" => $this->message,
"response_body" => $this->response_body, "response_body" => $this->response_body,
"status" => $this->status, "status" => $this->status,
"start_time" => $startTime ? $startTime->toDateTimeString() : null,
"duration" => ($startTime && $finishTime)
? $finishTime->diff($startTime)->format('%H:%I:%S')
: null,
"finish_time" => $finishTime ? $finishTime->toDateTimeString() : null,
"created_at" => $this->created_at->toDateTimeString(), "created_at" => $this->created_at->toDateTimeString(),
"updated_at" => $this->updated_at->toDateTimeString(), "updated_at" => $this->updated_at->toDateTimeString(),
"failed_uuid" => $this->failed_uuid
]; ];
} }
} }

View File

@@ -14,6 +14,16 @@ class MenuResource extends JsonResource
*/ */
public function toArray(Request $request): array public function toArray(Request $request): array
{ {
return parent::toArray($request); // return parent::toArray($request);
return [
'id' => $this->id,
'name' => $this->name,
'icon' => $this->icon,
'url' => $this->url,
'sort_order' => $this->sort_order,
'parent' => $this->parent ? new MenuResource($this->parent) : null,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at
];
} }
} }

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Jobs;
use App\Enums\ImportDatasourceStatus;
use App\Models\BigdataResume;
use App\Models\ImportDatasource;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use App\Services\ServiceTabPbgTask;
use App\Services\ServiceGoogleSheet;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class RetrySyncronizeJob implements ShouldQueue
{
use Queueable, Dispatchable, InteractsWithQueue, SerializesModels;
private $import_datasource_id;
public function __construct(int $import_datasource_id)
{
$this->import_datasource_id = $import_datasource_id;
}
/**
* Execute the job.
*/
public function handle(): void
{
try{
$service_tab_pbg_task = app(ServiceTabPbgTask::class);
$service_google_sheet = app(ServiceGoogleSheet::class);
$failed_import = ImportDatasource::find($this->import_datasource_id);
$failed_import->update([
'message' => "Retrying from UUID: ". $failed_import->failed_uuid,
'status' => ImportDatasourceStatus::Processing->value,
'start_time' => now()
]);
$current_failed_uuid = null;
try{
$service_tab_pbg_task->run_service($failed_import->failed_uuid);
}catch(\Exception $e){
$current_failed_uuid = $service_tab_pbg_task->getFailedUUID();
throw $e;
}
$data_setting_result = $service_google_sheet->get_big_resume_data();
BigdataResume::generateResumeData($failed_import->id, "all", $data_setting_result);
BigdataResume::generateResumeData($failed_import->id, now()->year, $data_setting_result);
$failed_import->update([
'status' => ImportDatasourceStatus::Success->value,
'message' => "Retry completed successfully from UUID: ". $failed_import->failed_uuid,
'finish_time' => now(),
'failed_uuid' => null
]);
}catch(\Exception $e){
\Log::error("RetrySyncronizeJob Failed: ". $e->getMessage(), [
'exception' => $e,
]);
if(isset($failed_import)){
$failed_import->update([
'status' => ImportDatasourceStatus::Failed->value,
'message' => "Retry failed from UUID: ". $failed_import->failed_uuid,
'finish_time' => now(),
'failed_uuid' => $current_failed_uuid
]);
}
$this->fail($e);
}
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace App\Jobs;
use App\Models\BigdataResume;
use App\Models\ImportDatasource;
use App\Services\ServiceGoogleSheet;
use App\Services\ServicePbgTask;
use App\Services\ServiceTabPbgTask;
use App\Services\ServiceTokenSIMBG;
use GuzzleHttp\Client;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class ScrapingDataJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Inject dependencies instead of creating them inside.
*/
public function __construct(
) {
}
/**
* Execute the job.
*/
public function handle()
{
try {
$client = app(Client::class);
$service_pbg_task = app(ServicePbgTask::class);
$service_tab_pbg_task = app(ServiceTabPbgTask::class);
$service_google_sheet = app(ServiceGoogleSheet::class);
$service_token = app(ServiceTokenSIMBG::class);
// Create a record with "processing" status
$import_datasource = ImportDatasource::create([
'message' => 'Initiating scraping...',
'response_body' => null,
'status' => 'processing',
'start_time' => now(),
'failed_uuid' => null
]);
$failed_uuid = null;
// Run the scraping services
$service_google_sheet->run_service();
$service_pbg_task->run_service();
try{
$service_tab_pbg_task->run_service();
}catch(\Exception $e){
$failed_uuid = $service_tab_pbg_task->getFailedUUID();
throw $e;
}
$data_setting_result = $service_google_sheet->get_big_resume_data();
BigdataResume::generateResumeData($import_datasource->id, "all", $data_setting_result);
BigdataResume::generateResumeData($import_datasource->id, now()->year, $data_setting_result);
// Update status to success
$import_datasource->update([
'status' => 'success',
'message' => 'Scraping completed successfully.',
'finish_time' => now()
]);
} catch (\Exception $e) {
Log::error('Scraping failed: ' . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
// Update status to failed
if (isset($import_datasource)) {
$import_datasource->update([
'status' => 'failed',
'response_body' => 'Error: ' . $e->getMessage(),
'finish_time' => now(),
'failed_uuid' => $failed_uuid,
]);
}
// Mark the job as failed
$this->fail($e);
}
}
}

View File

@@ -2,8 +2,12 @@
namespace App\Jobs; namespace App\Jobs;
use App\Models\ImportDatasource;
use App\Services\GoogleSheetService; use App\Services\GoogleSheetService;
use App\Services\ServiceSIMBG; use App\Services\ServiceGoogleSheet;
use App\Services\ServicePbgTask;
use App\Services\ServiceTabPbgTask;
use GuzzleHttp\Client;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
@@ -18,17 +22,58 @@ class SyncronizeSIMBG implements ShouldQueue
public function __construct() public function __construct()
{ {
// Avoid injecting non-serializable dependencies here
} }
public function handle(): void public function handle(): void
{ {
$import_datasource = ImportDatasource::where('status', 'processing')->first();
if (!$import_datasource) {
$import_datasource = ImportDatasource::create([
'message' => 'Initiating scraping...',
'response_body' => null,
'status' => 'processing'
]);
}
try { try {
$serviceSIMBG = app(ServiceSIMBG::class); // Create an instance of GuzzleHttp\Client inside handle()
$serviceSIMBG->syncTaskPBG(); $client = new Client();
// Create instances of services inside handle()
$service_pbg_task = app(ServicePbgTask::class);
$service_tab_pbg_task = app(ServiceTabPbgTask::class);
// Create a record with "processing" status
// Run the service
$service_google_sheet = new ServiceGoogleSheet();
\Log::info('Starting Google Sheet service');
$service_google_sheet->run_service();
\Log::info('Google Sheet service completed');
\Log::info('Starting PBG Task service');
$service_pbg_task->run_service();
\Log::info('PBG Task service completed');
\Log::info('Starting Tab PBG Task service');
$service_tab_pbg_task->run_service();
\Log::info('Tab PBG Task service completed');
// Update the record status to "success" after completion
$import_datasource->update([
'status' => 'success',
'message' => 'Scraping completed successfully.'
]);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::error("SyncronizeSIMBG Job Failed: " . $e->getMessage(), [ \Log::error("SyncronizeSIMBG Job Failed: " . $e->getMessage(), [
'exception' => $e, 'exception' => $e,
]); ]);
$import_datasource->update([
'status' => 'failed',
'message' => 'Failed job'
]);
$this->fail($e); // Mark the job as failed $this->fail($e); // Mark the job as failed
} }
} }

View File

@@ -13,6 +13,8 @@ class ImportDatasource extends Model
'id', 'id',
'message', 'message',
'response_body', 'response_body',
'status' 'status',
'start_time',
'finish_time'
]; ];
} }

View File

@@ -22,4 +22,7 @@ class Menu extends Model
public function children(){ public function children(){
return $this->hasMany(Menu::class,'parent_id'); return $this->hasMany(Menu::class,'parent_id');
} }
public function parent(){
return $this->belongsTo(Menu::class,'parent_id');
}
} }

View File

@@ -4,6 +4,7 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail; // use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
@@ -11,7 +12,7 @@ use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable class User extends Authenticatable
{ {
/** @use HasFactory<\Database\Factories\UserFactory> */ /** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable, HasApiTokens; use HasFactory, Notifiable, HasApiTokens, SoftDeletes;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@@ -27,6 +28,8 @@ class User extends Authenticatable
'position' 'position'
]; ];
protected $dates = ['deleted_at'];
/** /**
* The attributes that should be hidden for serialization. * The attributes that should be hidden for serialization.
* *
@@ -50,6 +53,12 @@ class User extends Authenticatable
]; ];
} }
public function delete(){
$this->email = $this->email . '_deleted_'. now()->timestamp;
$this->save();
return parent::delete();
}
public function roles(){ public function roles(){
return $this->belongsToMany(Role::class, 'user_role')->withTimestamps(); return $this->belongsToMany(Role::class, 'user_role')->withTimestamps();
} }

View File

@@ -3,8 +3,13 @@
namespace App\Providers; namespace App\Providers;
use App\Models\Menu; use App\Models\Menu;
use App\Services\ServiceGoogleSheet;
use App\Services\ServicePbgTask;
use App\Services\ServiceTabPbgTask;
use App\Services\ServiceTokenSIMBG;
use App\View\Components\Circle; use App\View\Components\Circle;
use Auth; use Auth;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
@@ -19,11 +24,24 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function register(): void public function register(): void
{ {
$this->app->singleton(GoogleSheetService::class, function () { $this->app->bind(Client::class, function () {
return new GoogleSheetService(); return new Client();
}); });
$this->app->singleton(ServiceSIMBG::class, function ($app) {
return new ServiceSIMBG($app->make(GoogleSheetService::class)); $this->app->bind(ServiceTokenSIMBG::class, function ($app) {
return new ServiceTokenSIMBG();
});
$this->app->bind(ServicePbgTask::class, function ($app) {
return new ServicePbgTask($app->make(Client::class), $app->make(ServiceTokenSIMBG::class));
});
$this->app->bind(ServiceTabPbgTask::class, function ($app) {
return new ServiceTabPbgTask($app->make(Client::class), $app->make(ServiceTokenSIMBG::class));
});
$this->app->bind(ServiceGoogleSheet::class, function ($app) {
return new ServiceGoogleSheet();
}); });
} }

View File

@@ -0,0 +1,321 @@
<?php
namespace App\Services;
use App\Models\DataSetting;
use App\Models\ImportDatasource;
use App\Models\PbgTaskGoogleSheet;
use Carbon\Carbon;
use Exception;
use Google_Client;
use Google_Service_Sheets;
use Log;
class ServiceGoogleSheet
{
protected $client;
protected $service;
protected $spreadsheetID;
protected $service_sheets;
protected $import_datasource;
public function __construct()
{
$this->client = new Google_Client();
$this->client->setApplicationName("Sibedas Google Sheets API");
$this->client->setScopes([Google_Service_Sheets::SPREADSHEETS_READONLY]);
$this->client->setAuthConfig(storage_path("app/teak-banner-450003-s8-ea05661d9db0.json"));
$this->client->setAccessType("offline");
$this->service = new Google_Service_Sheets($this->client);
$this->spreadsheetID = env("SPREAD_SHEET_ID");
$this->service_sheets = new Google_Service_Sheets($this->client);
}
public function run_service(){
try{
$this->sync_big_data();
$this->sync_google_sheet_data();
}catch(Exception $e){
throw $e;
}
}
public function sync_google_sheet_data() {
try {
$sheet_data = $this->get_data_by_sheet(0);
if (empty($sheet_data) || count($sheet_data) < 2) {
Log::warning("sync_google_sheet_data: No valid data found.");
throw new Exception("sync_google_sheet_data: No valid data found.");
}
$cleanValue = function ($value) {
return (isset($value) && trim($value) !== '') ? trim($value) : null;
};
$mapUpsert = [];
foreach(array_slice($sheet_data, 1) as $row){
if(!is_array($row)){
continue;
}
$no_registrasi = $cleanValue($row[2] ?? null);
// Apply the same logic from your SQL UPDATE
if (strpos($no_registrasi, 'PBG-') === 0) {
$format_registrasi = $no_registrasi;
} else {
$format_registrasi = sprintf(
"PBG-%s-%s-%s",
substr($no_registrasi, 0, 6) ?: '',
substr($no_registrasi, 7, 8) ?: '',
substr($no_registrasi, -2) ?: ''
);
}
$mapUpsert[] = [
'jenis_konsultasi' => $cleanValue($row[1] ?? null),
'no_registrasi' => $no_registrasi,
'formatted_registration_number' => $format_registrasi,
'nama_pemilik' => $cleanValue($row[3] ?? null),
'lokasi_bg' => $cleanValue($row[4] ?? null),
'fungsi_bg' => $cleanValue($row[5] ?? null),
'nama_bangunan' => $cleanValue($row[6] ?? null),
'tgl_permohonan' => $this->convertToDate($cleanValue($row[7] ?? null)),
'status_verifikasi' => $cleanValue($row[8] ?? null),
'status_permohonan' => $cleanValue($row[9] ?? null),
'alamat_pemilik' => $cleanValue($row[10] ?? null),
'no_hp' => $cleanValue($row[11] ?? null),
'email' => $cleanValue($row[12] ?? null),
'tanggal_catatan' => $this->convertToDate($cleanValue($row[13] ?? null)),
'catatan_kekurangan_dokumen' => $cleanValue($row[14] ?? null),
'gambar' => $cleanValue($row[15] ?? null),
'krk_kkpr' => $cleanValue($row[16] ?? null),
'no_krk' => $cleanValue($row[17] ?? null),
'lh' => $cleanValue($row[18] ?? null),
'ska' => $cleanValue($row[19] ?? null),
'keterangan' => $cleanValue($row[20] ?? null),
'helpdesk' => $cleanValue($row[21] ?? null),
'pj' => $cleanValue($row[22] ?? null),
'kepemilikan' => $cleanValue($row[24] ?? null),
'potensi_taru' => $cleanValue($row[25] ?? null),
'validasi_dinas' => $cleanValue($row[26] ?? null),
'kategori_retribusi' => $cleanValue($row[27] ?? null),
'no_urut_ba_tpt' => $cleanValue($row[28] ?? null),
'tanggal_ba_tpt' => $this->convertToDate($cleanValue($row[29] ?? null)),
'no_urut_ba_tpa' => $cleanValue($row[30] ?? null),
'tanggal_ba_tpa' => $this->convertToDate($cleanValue($row[31] ?? null)),
'no_urut_skrd' => $cleanValue($row[32] ?? null),
'tanggal_skrd' => $this->convertToDate($cleanValue($row[33] ?? null)),
'ptsp' => $cleanValue($row[34] ?? null),
'selesai_terbit' => $cleanValue($row[35] ?? null),
'tanggal_pembayaran' => $cleanValue($row[36] ?? null),
'format_sts' => $cleanValue($row[37] ?? null),
'tahun_terbit' => (int) $cleanValue($row[38] ?? null),
'tahun_berjalan' => (int) $cleanValue($row[39] ?? null),
'kelurahan' => $cleanValue($row[40] ?? null),
'kecamatan' => $cleanValue($row[41] ?? null),
'lb' => $this->convertToDecimal($cleanValue($row[42] ?? 0)),
'tb' => $this->convertToDecimal($cleanValue($row[43] ?? 0)),
'jlb' => (int) $cleanValue($row[44] ?? null),
'unit' => (int) $cleanValue($row[45] ?? null),
'usulan_retribusi' => (int) $cleanValue($row[46] ?? null),
'nilai_retribusi_keseluruhan_simbg' => $this->convertToDecimal($cleanValue($row[47] ?? 0)),
'nilai_retribusi_keseluruhan_pad' => $this->convertToDecimal($cleanValue($row[48] ?? 0)),
'denda' => $this->convertToDecimal($cleanValue($row[49] ?? 0)),
'latitude' => $cleanValue($row[50] ?? null),
'longitude' => $cleanValue($row[51] ?? null),
'nik_nib' => $cleanValue($row[52] ?? null),
'dok_tanah' => $cleanValue($row[53] ?? null),
'temuan' => $cleanValue($row[54] ?? null),
'updated_at' => now()
];
}
// Count occurrences of each no_registrasi
$registrasiCounts = array_count_values(array_column($mapUpsert, 'no_registrasi'));
// Filter duplicates (those appearing more than once)
$duplicates = array_filter($registrasiCounts, function ($count) {
return $count > 1;
});
if (!empty($duplicates)) {
Log::warning("Duplicate no_registrasi found", ['duplicates' => array_keys($duplicates)]);
}
// Remove duplicates before upsert
$mapUpsert = collect($mapUpsert)->unique('no_registrasi')->values()->all();
$batchSize = 1000;
$chunks = array_chunk($mapUpsert, $batchSize);
foreach ($chunks as $chunk) {
PbgTaskGoogleSheet::upsert($chunk, ['no_registrasi']);
}
Log::info("sync google sheet done");
return true;
} catch (\Exception $e) {
Log::error("sync_google_sheet_data failed", ['error' => $e->getMessage()]);
throw $e;
}
}
public function sync_big_data(){
try {
$sheet_big_data = $this->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;
}
}
foreach ($data_setting_result as $key => $value) {
DataSetting::updateOrInsert(
["key" => $key], // Find by key
["value" => $value] // Update or insert value
);
}
return true;
} catch (\Exception $e) {
// **Log error**
Log::error("Error syncing Google Sheet data", ['error' => $e->getMessage()]);
return false;
}
}
public function get_big_resume_data(){
try {
$sheet_big_data = $this->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;
}
}
return $data_setting_result;
}catch(Exception $exception){
Log::error("Error getting big resume data", ['error' => $exception->getMessage()]);
throw $exception;
}
}
private function get_data_by_sheet($no_sheet = 1){
$spreadsheet = $this->service->spreadsheets->get($this->spreadsheetID);
$sheets = $spreadsheet->getSheets();
$sheetTitle = $sheets[$no_sheet]->getProperties()->getTitle();
$range = "{$sheetTitle}";
$response = $this->service->spreadsheets_values->get($this->spreadsheetID, $range);
$values = $response->getValues();
return!empty($values)? $values : [];
}
private 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;
}
private 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;
}
private 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

@@ -0,0 +1,171 @@
<?php
namespace App\Services;
use App\Models\GlobalSetting;
use App\Models\PbgTask;
use Carbon\Carbon;
use GuzzleHttp\Client;
use Exception;
use Illuminate\Support\Facades\Log;
class ServicePbgTask
{
private $client;
private $simbg_host;
private $fetch_per_page;
private $pbg_task_url;
private $service_token;
private $user_token;
private $user_refresh_token;
public function __construct(Client $client, ServiceTokenSIMBG $service_token)
{
$settings = GlobalSetting::whereIn('key', ['SIMBG_HOST', 'FETCH_PER_PAGE'])
->pluck('value', 'key');
$this->simbg_host = trim((string) ($settings['SIMBG_HOST'] ?? ""));
$this->fetch_per_page = trim((string) ($settings['FETCH_PER_PAGE'] ?? "10"));
$this->client = $client;
$this->service_token = $service_token;
$this->pbg_task_url = "{$this->simbg_host}/api/pbg/v1/list/?page=1&size={$this->fetch_per_page}&sort=ASC";
$auth_data = $this->service_token->get_token();
$this->user_token = $auth_data['access'];
$this->user_refresh_token = $auth_data['refresh'];
}
public function run_service()
{
try{
$this->fetch_pbg_task();
}catch(Exception $e){
throw $e;
}
}
private function fetch_pbg_task()
{
try {
$currentPage = 1;
$totalPage = 1;
$options = [
'headers' => [
'Authorization' => "Bearer {$this->user_token}",
'Content-Type' => 'application/json'
]
];
$maxRetries = 3; // Maximum number of retries
$initialDelay = 1; // Initial delay in seconds
$fetchData = function ($url) use (&$options, $maxRetries, $initialDelay) {
$retryCount = 0;
while ($retryCount < $maxRetries) {
try {
return $this->client->get($url, $options);
} catch (\GuzzleHttp\Exception\ClientException $e) {
if ($e->getCode() === 401) {
Log::warning("Unauthorized. Refreshing token...");
// Refresh token
$auth_data = $this->service_token->refresh_token($this->user_refresh_token);
if (!isset($auth_data['access'])) {
Log::error("Token refresh failed.");
throw new Exception("Token refresh failed.");
}
// Update tokens
$this->user_token = $auth_data['access'];
$this->user_refresh_token = $auth_data['refresh'];
// Update headers
$options['headers']['Authorization'] = "Bearer {$this->user_token}";
// Retry request
return $this->client->get($url, $options);
}
throw $e;
} catch (\GuzzleHttp\Exception\ServerException | \GuzzleHttp\Exception\ConnectException $e) {
// Handle 502 or connection issues
if ($e->getCode() === 502) {
Log::warning("502 Bad Gateway - Retrying in {$initialDelay} seconds...");
} else {
Log::error("Network error - Retrying in {$initialDelay} seconds...");
}
$retryCount++;
sleep($initialDelay);
$initialDelay *= 2; // Exponential backoff
}
}
Log::error("Max retries reached. Failing request.");
throw new Exception("Max retries reached. Failing request.");
};
do {
$url = "{$this->simbg_host}/api/pbg/v1/list/?page={$currentPage}&size={$this->fetch_per_page}&sort=ASC";
$fetch_data = $fetchData($url);
if (!$fetch_data) {
Log::error("Failed to fetch data on page {$currentPage} after retries.");
throw new Exception("Failed to fetch data on page {$currentPage} after retries.");
}
$response = json_decode($fetch_data->getBody()->getContents(), true);
if (!isset($response['data'])) {
Log::error("Invalid API response on page {$currentPage}");
throw new Exception("Invalid API response on page {$currentPage}");
}
$data = $response['data'];
$totalPage = isset($response['total_page']) ? (int) $response['total_page'] : 1;
$saved_data = [];
foreach ($data as $item) {
$saved_data[] = [
'uuid' => $item['uid'] ?? null,
'name' => $item['name'] ?? null,
'owner_name' => $item['owner_name'] ?? null,
'application_type' => $item['application_type'] ?? null,
'application_type_name' => $item['application_type_name'] ?? null,
'condition' => $item['condition'] ?? null,
'registration_number' => $item['registration_number'] ?? null,
'document_number' => $item['document_number'] ?? null,
'address' => $item['address'] ?? null,
'status' => $item['status'] ?? null,
'status_name' => $item['status_name'] ?? null,
'slf_status' => $item['slf_status'] ?? null,
'slf_status_name' => $item['slf_status_name'] ?? null,
'function_type' => $item['function_type'] ?? null,
'consultation_type' => $item['consultation_type'] ?? null,
'due_date' => $item['due_date'] ?? null,
'land_certificate_phase' => $item['land_certificate_phase'] ?? null,
'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(),
];
}
if (!empty($saved_data)) {
PbgTask::upsert($saved_data, ['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'
]);
}
$currentPage++;
} while ($currentPage <= $totalPage);
return true;
} catch (Exception $e) {
Log::error("Error fetching PBG tasks", ['error' => $e->getMessage()]);
throw $e;
}
}
}

View File

@@ -180,58 +180,57 @@ class ServiceSIMBG
$mapToUpsert = []; $mapToUpsert = [];
foreach ($sheetData as $data) { foreach ($sheetData as $data) {
$mapToUpsert[] = $mapToUpsert[] = [
[ 'no_registrasi' => $this->cleanString($data['no__registrasi'] ?? null),
'no_registrasi' => $data['no__registrasi'] ?? null, 'jenis_konsultasi' => $this->cleanString($data['jenis_konsultasi'] ?? null),
'jenis_konsultasi' => $data['jenis_konsultasi'] ?? null, 'fungsi_bg' => $this->cleanString($data['fungsi_bg'] ?? null),
'fungsi_bg' => $data['fungsi_bg'] ?? null, 'tgl_permohonan' => $this->convertToDate($this->cleanString($data['tgl_permohonan'] ?? null)),
'tgl_permohonan' => $this->convertToDate($data['tgl_permohonan']), 'status_verifikasi' => $this->cleanString($data['status_verifikasi'] ?? null),
'status_verifikasi' => $data['status_verifikasi'] ?? null, 'status_permohonan' => $this->convertToDate($this->cleanString($data['status_permohonan'] ?? null)),
'status_permohonan' => $this->convertToDate($data['status_permohonan']), 'alamat_pemilik' => $this->cleanString($data['alamat_pemilik'] ?? null),
'alamat_pemilik' => $data['alamat_pemilik'] ?? null, 'no_hp' => $this->cleanString($data['no__hp'] ?? null),
'no_hp' => $data['no__hp'] ?? null, 'email' => $this->cleanString($data['e_mail'] ?? null),
'email' => $data['e_mail'] ?? null, 'tanggal_catatan' => $this->convertToDate($this->cleanString($data['tanggal_catatan'] ?? null)),
'tanggal_catatan' => $this->convertToDate($data['tanggal_catatan']), 'catatan_kekurangan_dokumen' => $this->cleanString($data['catatan_kekurangan_dokumen'] ?? null),
'catatan_kekurangan_dokumen' => $data['catatan_kekurangan_dokumen'] ?? null, 'gambar' => $this->cleanString($data['gambar'] ?? null),
'gambar' => $data['gambar'] ?? null, 'krk_kkpr' => $this->cleanString($data['krk_kkpr'] ?? null),
'krk_kkpr' => $data['krk_kkpr'] ?? null, 'no_krk' => $this->cleanString($data['no__krk'] ?? null),
'no_krk' => $data['no__krk'] ?? null, 'lh' => $this->cleanString($data['lh'] ?? null),
'lh' => $data['lh'] ?? null, 'ska' => $this->cleanString($data['ska'] ?? null),
'ska' => $data['ska'] ?? null, 'keterangan' => $this->cleanString($data['keterangan'] ?? null),
'keterangan' => $data['keterangan'] ?? null, 'helpdesk' => $this->cleanString($data['helpdesk'] ?? null),
'helpdesk' => $data['helpdesk'] ?? null, 'pj' => $this->cleanString($data['pj'] ?? null),
'pj' => $data['pj'] ?? null, 'kepemilikan' => $this->cleanString($data['kepemilikan'] ?? null),
'kepemilikan' => $data['kepemilikan'] ?? null, 'potensi_taru' => $this->cleanString($data['potensi_taru'] ?? null),
'potensi_taru' => $data['potensi_taru'] ?? null, 'validasi_dinas' => $this->cleanString($data['validasi_dinas'] ?? null),
'validasi_dinas' => $data['validasi_dinas'] ?? null, 'kategori_retribusi' => $this->cleanString($data['kategori_retribusi'] ?? null),
'kategori_retribusi' => $data['kategori_retribusi'] ?? null, 'no_urut_ba_tpt' => $this->cleanString($data['no__urut_ba_tpt__2024_0001_'] ?? null),
'no_urut_ba_tpt' => $data['no__urut_ba_tpt__2024_0001_'] ?? null, 'tanggal_ba_tpt' => $this->convertToDate($this->cleanString($data['tanggal_ba_tpt'] ?? null)),
'tanggal_ba_tpt' => $this->convertToDate($data['tanggal_ba_tpt']), 'no_urut_ba_tpa' => $this->cleanString($data['no__urut_ba_tpa'] ?? null),
'no_urut_ba_tpa' => $data['no__urut_ba_tpa'] ?? null, 'tanggal_ba_tpa' => $this->convertToDate($this->cleanString($data['tanggal_ba_tpa'] ?? null)),
'tanggal_ba_tpa' => $this->convertToDate($data['tanggal_ba_tpa']), 'no_urut_skrd' => $this->cleanString($data['no__urut_skrd__2024_0001_'] ?? null),
'no_urut_skrd' => $data['no__urut_skrd__2024_0001_'] ?? null, 'tanggal_skrd' => $this->convertToDate($this->cleanString($data['tanggal_skrd'] ?? null)),
'tanggal_skrd' => $this->convertToDate($data['tanggal_skrd']), 'ptsp' => $this->cleanString($data['ptsp'] ?? null),
'ptsp' => $data['ptsp'] ?? null, 'selesai_terbit' => $this->cleanString($data['selesai_terbit'] ?? null),
'selesai_terbit' => $data['selesai_terbit'] ?? null, 'tanggal_pembayaran' => $this->convertToDate($this->cleanString($data['tanggal_pembayaran__yyyy_mm_dd_'] ?? null)),
'tanggal_pembayaran' => $this->convertToDate($data['tanggal_pembayaran__yyyy_mm_dd_']), 'format_sts' => $this->cleanString($data['format_sts'] ?? null),
'format_sts' => $data['format_sts'] ?? null, 'tahun_terbit' => (int) ($data['tahun_terbit'] ?? null),
'tahun_terbit' => (int) $data['tahun_terbit'] ?? null, 'tahun_berjalan' => (int) ($data['tahun_berjalan'] ?? null),
'tahun_berjalan' => (int) $data['tahun_berjalan'] ?? null, 'kelurahan' => $this->cleanString($data['kelurahan'] ?? null),
'kelurahan' => $data['kelurahan'] ?? null, 'kecamatan' => $this->cleanString($data['kecamatan'] ?? null),
'kecamatan' => $data['kecamatan'] ?? null, 'lb' => $this->convertToDecimal($data['lb'] ?? null),
'lb' => $this->convertToDecimal($data['lb']) ?? null, 'tb' => $this->convertToDecimal($data['tb'] ?? null),
'tb' => $this->convertToDecimal($data['tb']) ?? null, 'jlb' => (int) ($data['jlb'] ?? null),
'jlb' => (int) $data['jlb'] ?? null, 'unit' => (int) ($data['unit'] ?? null),
'unit' => (int) $data['unit'] ?? null, 'usulan_retribusi' => (int) ($data['usulan_retribusi'] ?? null),
'usulan_retribusi' => (int) $data['usulan_retribusi'] ?? null, 'nilai_retribusi_keseluruhan_simbg' => $this->convertToDecimal($data['nilai_retribusi_keseluruhan__simbg_'] ?? 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),
'nilai_retribusi_keseluruhan_pad' => $this->convertToDecimal($data['nilai_retribusi_keseluruhan__pad_']) ?? null, 'denda' => $this->convertToDecimal($data['denda'] ?? null),
'denda' => $this->convertToDecimal($data['denda']) ?? null, 'latitude' => $this->cleanString($data['latitude'] ?? null),
'latitude' => $data['latitude'] ?? null, 'longitude' => $this->cleanString($data['longitude'] ?? null),
'longitude' => $data['longitude'] ?? null, 'nik_nib' => $this->cleanString($data['nik_nib'] ?? null),
'nik_nib' => $data['nik_nib'] ?? null, 'dok_tanah' => $this->cleanString($data['dok__tanah'] ?? null),
'dok_tanah' => $data['dok__tanah'] ?? null, 'temuan' => $this->cleanString($data['temuan'] ?? null),
'temuan' => $data['temuan'] ?? null,
]; ];
} }
@@ -656,4 +655,9 @@ class ServiceSIMBG
return null; return null;
} }
} }
private function cleanString($value)
{
return isset($value) ? trim(strip_tags($value)) : null;
}
} }

View File

@@ -0,0 +1,426 @@
<?php
namespace App\Services;
use App\Models\GlobalSetting;
use App\Models\PbgTask;
use App\Models\PbgTaskIndexIntegrations;
use App\Models\PbgTaskPrasarana;
use App\Models\PbgTaskRetributions;
use App\Models\TaskAssignment;
use Carbon\Carbon;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Log;
class ServiceTabPbgTask
{
private $client;
private $simbg_host;
private $fetch_per_page;
private $service_token;
private $user_token;
private $user_refresh_token;
protected $current_uuid = null;
public function __construct(Client $client, ServiceTokenSIMBG $service_token)
{
$settings = GlobalSetting::whereIn('key', ['SIMBG_HOST', 'FETCH_PER_PAGE'])
->pluck('value', 'key');
$this->simbg_host = trim((string) ($settings['SIMBG_HOST'] ?? ""));
$this->fetch_per_page = trim((string) ($settings['FETCH_PER_PAGE'] ?? "10"));
$this->client = $client;
$this->service_token = $service_token;
$auth_data = $this->service_token->get_token();
$this->user_token = $auth_data['access'];
$this->user_refresh_token = $auth_data['refresh'];
}
public function run_service($retry_uuid = null)
{
try {
$pbg_tasks = PbgTask::orderBy('id')->get();
$start = false;
foreach ($pbg_tasks as $pbg_task) {
if($retry_uuid){
if($pbg_task->uuid === $retry_uuid){
$start = true;
}
if(!$start){
continue;
}
}
try{
$this->current_uuid = $pbg_task->uuid;
$this->scraping_task_assignments($pbg_task->uuid);
$this->scraping_task_retributions($pbg_task->uuid);
$this->scraping_task_integrations($pbg_task->uuid);
}catch(\Exception $e){
Log::error("Failed on UUID: {$this->current_uuid}, Error: " . $e->getMessage());
throw $e;
}
}
} catch (\Exception $e) {
Log::error("Failed to syncronize: " . $e->getMessage());
throw $e;
}
}
public function getFailedUUID(){
return $this->current_uuid;
}
private function scraping_task_assignments($uuid)
{
$url = "{$this->simbg_host}/api/pbg/v1/list-tim-penilai/{$uuid}/?page=1&size=10";
$options = [
'headers' => [
'Authorization' => "Bearer {$this->user_token}",
'Content-Type' => 'application/json'
]
];
$maxRetries = 3;
$initialDelay = 1;
$retriedAfter401 = false;
for ($retryCount = 0; $retryCount < $maxRetries; $retryCount++) {
try {
$response = $this->client->get($url, $options);
$responseData = json_decode($response->getBody()->getContents(), true);
if (empty($responseData['data']) || !is_array($responseData['data'])) {
return true;
}
$task_assignments = [];
foreach ($responseData['data'] as $data) {
$task_assignments[] = [
'pbg_task_uid' => $uuid,
'user_id' => $data['user_id'] ?? null,
'name' => $data['name'] ?? null,
'username' => $data['username'] ?? null,
'email' => $data['email'] ?? null,
'phone_number' => $data['phone_number'] ?? null,
'role' => $data['role'] ?? null,
'role_name' => $data['role_name'] ?? null,
'is_active' => $data['is_active'] ?? false,
'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'] ?? false,
'uid' => $data['uid'] ?? null,
'status' => $data['status'] ?? null,
'status_name' => $data['status_name'] ?? null,
'note' => $data['note'] ?? null,
'ta_id' => $data['id'] ?? null,
'created_at' => now(),
'updated_at' => now(),
];
}
if (!empty($task_assignments)) {
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 $responseData;
} catch (\GuzzleHttp\Exception\ClientException $e) {
if ($e->getCode() === 401 && !$retriedAfter401) {
Log::warning("401 Unauthorized - Refreshing token and retrying...");
try{
$this->refreshToken();
$options['headers']['Authorization'] = "Bearer {$this->user_token}";
$retriedAfter401 = true;
continue;
}catch(\Exception $refreshError){
Log::error("Token refresh and login failed: " . $refreshError->getMessage());
return false;
}
}
throw $e;
} catch (\GuzzleHttp\Exception\ServerException | \GuzzleHttp\Exception\ConnectException $e) {
if ($e->getCode() === 502) {
Log::warning("502 Bad Gateway - Retrying in {$initialDelay} seconds...");
} else {
Log::error("Network error ({$e->getCode()}) - Retrying in {$initialDelay} seconds...");
}
sleep($initialDelay);
$initialDelay *= 2;
} catch (\Exception $e) {
Log::error("Unexpected error: " . $e->getMessage());
throw $e;
}
}
Log::error("Failed to fetch task assignments for UUID {$uuid} after {$maxRetries} retries.");
throw new \Exception("Failed to fetch task assignments for UUID {$uuid} after retries.");
}
private function scraping_task_retributions($uuid)
{
$url = "{$this->simbg_host}/api/pbg/v1/detail/" . $uuid . "/retribution/submit/";
$options = [
'headers' => [
'Authorization' => "Bearer {$this->user_token}",
'Content-Type' => 'application/json'
]
];
$maxRetries = 3;
$initialDelay = 1;
$retriedAfter401 = false;
for ($retryCount = 0; $retryCount < $maxRetries; $retryCount++) {
try {
$response = $this->client->get($url, $options);
$responseData = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
if (empty($responseData['data']) || !is_array($responseData['data'])) {
return true;
}
$data = $responseData['data'];
$detailCreatedAt = isset($data['created_at'])
? Carbon::parse($data['created_at'])->format('Y-m-d H:i:s')
: null;
$detailUpdatedAt = isset($data['updated_at'])
? Carbon::parse($data['updated_at'])->format('Y-m-d H:i:s')
: null;
$pbg_task_retributions = PbgTaskRetributions::updateOrCreate(
['detail_id' => $data['id']],
[
'detail_uid' => $data['uid'] ?? null,
'detail_created_at' => $detailCreatedAt ?? null,
'detail_updated_at' => $detailUpdatedAt ?? null,
'luas_bangunan' => $data['luas_bangunan'] ?? null,
'indeks_lokalitas' => $data['indeks_lokalitas'] ?? null,
'wilayah_shst' => $data['wilayah_shst'] ?? null,
'kegiatan_id' => $data['kegiatan']['id'] ?? null,
'kegiatan_name' => $data['kegiatan']['name'] ?? null,
'nilai_shst' => $data['nilai_shst'] ?? null,
'indeks_terintegrasi' => $data['indeks_terintegrasi'] ?? null,
'indeks_bg_terbangun' => $data['indeks_bg_terbangun'] ?? null,
'nilai_retribusi_bangunan' => $data['nilai_retribusi_bangunan'] ?? null,
'nilai_prasarana' => $data['nilai_prasarana'] ?? null,
'created_by' => $data['created_by'] ?? null,
'pbg_document' => $data['pbg_document'] ?? null,
'underpayment' => $data['underpayment'] ?? null,
'skrd_amount' => $data['skrd_amount'] ?? null,
'pbg_task_uid' => $uuid,
]
);
$pbg_task_retribution_id = $pbg_task_retributions->id;
$prasaranaData = $data['prasarana'] ?? [];
if (!empty($prasaranaData)) {
$insertData = array_map(fn($item) => [
'pbg_task_uid' => $uuid,
'pbg_task_retribution_id' => $pbg_task_retribution_id,
'prasarana_id' => $item['id'] ?? null,
'prasarana_type' => $item['prasarana_type'] ?? null,
'building_type' => $item['building_type'] ?? null,
'total' => $item['total'] ?? null,
'quantity' => $item['quantity'] ?? null,
'unit' => $item['unit'] ?? null,
'index_prasarana' => $item['index_prasarana'] ?? null,
], $prasaranaData);
PbgTaskPrasarana::upsert($insertData, ['prasarana_id']);
}
return $responseData;
} catch (\GuzzleHttp\Exception\ClientException $e) {
if ($e->getCode() === 401 && !$retriedAfter401) {
Log::warning("401 Unauthorized - Refreshing token and retrying...");
try{
$this->refreshToken();
$options['headers']['Authorization'] = "Bearer {$this->user_token}";
$retriedAfter401 = true;
continue;
}catch(\Exception $refreshError){
Log::error("Token refresh and login failed: " . $refreshError->getMessage());
return false;
}
}
return false;
} catch (\GuzzleHttp\Exception\ServerException | \GuzzleHttp\Exception\ConnectException $e) {
if ($e->getCode() === 502) {
Log::warning("502 Bad Gateway - Retrying in {$initialDelay} seconds...");
} else {
Log::error("Network error ({$e->getCode()}) - Retrying in {$initialDelay} seconds...");
}
sleep($initialDelay);
$initialDelay *= 2;
} catch (\GuzzleHttp\Exception\RequestException $e) {
Log::error("Request error ({$e->getCode()}): " . $e->getMessage());
return false;
} catch (\JsonException $e) {
Log::error("JSON decoding error: " . $e->getMessage());
return false;
} catch (\Throwable $e) {
Log::critical("Unhandled error: " . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
return false;
}
}
Log::error("Failed to fetch task retributions for UUID {$uuid} after retries.");
throw new \Exception("Failed to fetch task retributions for UUID {$uuid} after retries.");
}
private function scraping_task_integrations($uuid){
$url = "{$this->simbg_host}/api/pbg/v1/detail/" . $uuid . "/retribution/indeks-terintegrasi/";
$options = [
'headers' => [
'Authorization' => "Bearer {$this->user_token}",
'Content-Type' => 'application/json'
]
];
$maxRetries = 3;
$initialDelay = 1;
$retriedAfter401 = false;
for ($retryCount = 0; $retryCount < $maxRetries; $retryCount++) {
try {
$response = $this->client->get($url, $options);
$responseData = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
if (empty($responseData['data']) || !is_array($responseData['data'])) {
return true;
}
$data = $responseData['data'];
$integrations[] = [
'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,
];
if (!empty($integrations)) {
PbgTaskIndexIntegrations::upsert($integrations, ['pbg_task_uid'], ['indeks_fungsi_bangunan',
'indeks_parameter_kompleksitas', 'indeks_parameter_permanensi', 'indeks_parameter_ketinggian', 'faktor_kepemilikan', 'indeks_terintegrasi', 'total']);
}
return $responseData;
} catch (\GuzzleHttp\Exception\ClientException $e) {
if ($e->getCode() === 401 && !$retriedAfter401) {
Log::warning("401 Unauthorized - Refreshing token and retrying...");
try{
$this->refreshToken();
$options['headers']['Authorization'] = "Bearer {$this->user_token}";
$retriedAfter401 = true;
continue;
}catch(\Exception $refreshError){
Log::error("Token refresh and login failed: " . $refreshError->getMessage());
return false;
}
}
return false;
} catch (\GuzzleHttp\Exception\ServerException | \GuzzleHttp\Exception\ConnectException $e) {
if ($e->getCode() === 502) {
Log::warning("502 Bad Gateway - Retrying in {$initialDelay} seconds...");
} else {
Log::error("Network error ({$e->getCode()}) - Retrying in {$initialDelay} seconds...");
}
sleep($initialDelay);
$initialDelay *= 2;
} catch (\GuzzleHttp\Exception\RequestException $e) {
Log::error("Request error ({$e->getCode()}): " . $e->getMessage());
return false;
} catch (\JsonException $e) {
Log::error("JSON decoding error: " . $e->getMessage());
return false;
} catch (\Throwable $e) {
Log::critical("Unhandled error: " . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
return false;
}
}
Log::error("Failed to fetch task index integration for UUID {$uuid} after retries.");
throw new \Exception("Failed to fetch task index integration for UUID {$uuid} after retries.");
}
private function refreshToken()
{
$maxRetries = 3; // Maximum retry attempts
$attempt = 0;
while ($attempt < $maxRetries) {
try {
$attempt++;
Log::info("Attempt $attempt: Refreshing token...");
$newAuthToken = $this->service_token->refresh_token($this->user_refresh_token);
if (!isset($newAuthToken['access']) || !isset($newAuthToken['refresh'])) {
throw new \Exception("Invalid refresh token response.");
}
$this->user_token = $newAuthToken['access'];
$this->user_refresh_token = $newAuthToken['refresh'];
Log::info("Token refreshed successfully on attempt $attempt.");
return; // Exit function on success
} catch (\Exception $e) {
Log::error("Token refresh failed on attempt $attempt: " . $e->getMessage());
if ($attempt >= $maxRetries) {
Log::info("Max retries reached. Attempting to log in again...");
break;
}
sleep(30); // Wait for 30 seconds before retrying
}
}
// If refresh fails after retries, attempt re-login
$attempt = 0;
while ($attempt < $maxRetries) {
try {
$attempt++;
Log::info("Attempt $attempt: Re-logging in...");
$loginAgain = $this->service_token->get_token(); // Login again
if (!isset($loginAgain['access']) || !isset($loginAgain['refresh'])) {
throw new \Exception("Invalid login response.");
}
$this->user_token = $loginAgain['access'];
$this->user_refresh_token = $loginAgain['refresh'];
Log::info("Re-login successful on attempt $attempt.");
return; // Exit function on success
} catch (\Exception $e) {
Log::error("Re-login failed on attempt $attempt: " . $e->getMessage());
if ($attempt >= $maxRetries) {
throw new \Exception("Both token refresh and login failed after $maxRetries attempts. " . $e->getMessage());
}
sleep(30); // Wait for 30 seconds before retrying
}
}
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Services;
use App\Models\GlobalSetting;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Support\Facades\Log;
class ServiceTokenSIMBG
{
private $client;
private $login_url;
private $email;
private $password;
private $simbg_host;
private $fetch_per_page;
private $refresh_url;
public function __construct()
{
$settings = GlobalSetting::whereIn('key', [
'SIMBG_EMAIL', 'SIMBG_PASSWORD', 'SIMBG_HOST', 'FETCH_PER_PAGE'
])->pluck('value', 'key');
$this->email = trim((string) ($settings['SIMBG_EMAIL'] ?? ""));
$this->password = trim((string) ($settings['SIMBG_PASSWORD'] ?? ""));
$this->simbg_host = trim((string) ($settings['SIMBG_HOST'] ?? ""));
$this->fetch_per_page = trim((string) ($settings['FETCH_PER_PAGE'] ?? ""));
$this->client = new Client();
$this->login_url = $this->simbg_host . "/api/user/v1/auth/login/";
$this->refresh_url = $this->simbg_host. "/api/user/v1/auth/token/refresh/";
}
public function get_token(){
try {
$response = $this->client->request('POST', $this->login_url, [
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'email' => $this->email,
'password' => $this->password
]
]);
$data = json_decode($response->getBody()->getContents(), true);
return $data['token'];
} catch (RequestException $e) {
Log::error("Failed to get token", [
'error' => $e->getMessage(),
'response' => $e->getResponse() ? $e->getResponse()->getBody()->getContents() : null
]);
return null;
}
}
public function refresh_token(string $refresh_token){
try {
$response = $this->client->request('POST', $this->refresh_url, [
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'refresh' => $refresh_token
]
]);
$data = json_decode($response->getBody()->getContents(), true);
return $data;
} catch (\Throwable $th) {
Log::error("Failed to refresh token", [
'error' => $th->getMessage()
]);
return null;
}
}
}

View File

@@ -10,6 +10,7 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"barryvdh/laravel-dompdf": "^3.1",
"google/apiclient": "^2.12", "google/apiclient": "^2.12",
"guzzlehttp/guzzle": "^7.9", "guzzlehttp/guzzle": "^7.9",
"laravel/framework": "^11.31", "laravel/framework": "^11.31",

370
composer.lock generated
View File

@@ -4,8 +4,85 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "41bb51871a746904ab745e4095db8b46", "content-hash": "e657a4f0a463fa048a0110c08babba93",
"packages": [ "packages": [
{
"name": "barryvdh/laravel-dompdf",
"version": "v3.1.1",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-dompdf.git",
"reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d",
"reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d",
"shasum": ""
},
"require": {
"dompdf/dompdf": "^3.0",
"illuminate/support": "^9|^10|^11|^12",
"php": "^8.1"
},
"require-dev": {
"larastan/larastan": "^2.7|^3.0",
"orchestra/testbench": "^7|^8|^9|^10",
"phpro/grumphp": "^2.5",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"PDF": "Barryvdh\\DomPDF\\Facade\\Pdf",
"Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf"
},
"providers": [
"Barryvdh\\DomPDF\\ServiceProvider"
]
},
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Barryvdh\\DomPDF\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "A DOMPDF Wrapper for Laravel",
"keywords": [
"dompdf",
"laravel",
"pdf"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-dompdf/issues",
"source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.1"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2025-02-13T15:07:54+00:00"
},
{ {
"name": "brick/math", "name": "brick/math",
"version": "0.12.1", "version": "0.12.1",
@@ -538,6 +615,161 @@
], ],
"time": "2024-02-05T11:56:58+00:00" "time": "2024-02-05T11:56:58+00:00"
}, },
{
"name": "dompdf/dompdf",
"version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
"reference": "a51bd7a063a65499446919286fb18b518177155a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/a51bd7a063a65499446919286fb18b518177155a",
"reference": "a51bd7a063a65499446919286fb18b518177155a",
"shasum": ""
},
"require": {
"dompdf/php-font-lib": "^1.0.0",
"dompdf/php-svg-lib": "^1.0.0",
"ext-dom": "*",
"ext-mbstring": "*",
"masterminds/html5": "^2.0",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"ext-gd": "*",
"ext-json": "*",
"ext-zip": "*",
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "^3.5",
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
},
"suggest": {
"ext-gd": "Needed to process images",
"ext-gmagick": "Improves image processing performance",
"ext-imagick": "Improves image processing performance",
"ext-zlib": "Needed for pdf stream compression"
},
"type": "library",
"autoload": {
"psr-4": {
"Dompdf\\": "src/"
},
"classmap": [
"lib/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "The Dompdf Community",
"homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
}
],
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
"homepage": "https://github.com/dompdf/dompdf",
"support": {
"issues": "https://github.com/dompdf/dompdf/issues",
"source": "https://github.com/dompdf/dompdf/tree/v3.1.0"
},
"time": "2025-01-15T14:09:04+00:00"
},
{
"name": "dompdf/php-font-lib",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-font-lib.git",
"reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d",
"reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6"
},
"type": "library",
"autoload": {
"psr-4": {
"FontLib\\": "src/FontLib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "The FontLib Community",
"homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse, export and make subsets of different types of font files.",
"homepage": "https://github.com/dompdf/php-font-lib",
"support": {
"issues": "https://github.com/dompdf/php-font-lib/issues",
"source": "https://github.com/dompdf/php-font-lib/tree/1.0.1"
},
"time": "2024-12-02T14:37:59+00:00"
},
{
"name": "dompdf/php-svg-lib",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-svg-lib.git",
"reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af",
"reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0",
"sabberworm/php-css-parser": "^8.4"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Svg\\": "src/Svg"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "The SvgLib Community",
"homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse and export to PDF SVG files.",
"homepage": "https://github.com/dompdf/php-svg-lib",
"support": {
"issues": "https://github.com/dompdf/php-svg-lib/issues",
"source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0"
},
"time": "2024-04-29T13:26:35+00:00"
},
{ {
"name": "dragonmantank/cron-expression", "name": "dragonmantank/cron-expression",
"version": "v3.4.0", "version": "v3.4.0",
@@ -2794,6 +3026,73 @@
}, },
"time": "2022-12-02T22:17:43+00:00" "time": "2022-12-02T22:17:43+00:00"
}, },
{
"name": "masterminds/html5",
"version": "2.9.0",
"source": {
"type": "git",
"url": "https://github.com/Masterminds/html5-php.git",
"reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6",
"reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6",
"shasum": ""
},
"require": {
"ext-dom": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Masterminds\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Matt Butcher",
"email": "technosophos@gmail.com"
},
{
"name": "Matt Farina",
"email": "matt@mattfarina.com"
},
{
"name": "Asmir Mustafic",
"email": "goetas@gmail.com"
}
],
"description": "An HTML5 parser and serializer.",
"homepage": "http://masterminds.github.io/html5-php",
"keywords": [
"HTML5",
"dom",
"html",
"parser",
"querypath",
"serializer",
"xml"
],
"support": {
"issues": "https://github.com/Masterminds/html5-php/issues",
"source": "https://github.com/Masterminds/html5-php/tree/2.9.0"
},
"time": "2024-03-31T07:05:07+00:00"
},
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
"version": "3.8.1", "version": "3.8.1",
@@ -4695,6 +4994,71 @@
], ],
"time": "2024-04-27T21:32:50+00:00" "time": "2024-04-27T21:32:50+00:00"
}, },
{
"name": "sabberworm/php-css-parser",
"version": "v8.7.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
"reference": "f414ff953002a9b18e3a116f5e462c56f21237cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/f414ff953002a9b18e3a116f5e462c56f21237cf",
"reference": "f414ff953002a9b18e3a116f5e462c56f21237cf",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
},
"require-dev": {
"phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.40"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Sabberworm\\CSS\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Raphael Schweikert"
},
{
"name": "Oliver Klee",
"email": "github@oliverklee.de"
},
{
"name": "Jake Hotson",
"email": "jake.github@qzdesign.co.uk"
}
],
"description": "Parser for CSS Files written in PHP",
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
"keywords": [
"css",
"parser",
"stylesheet"
],
"support": {
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.7.0"
},
"time": "2024-10-27T17:38:32+00:00"
},
{ {
"name": "symfony/clock", "name": "symfony/clock",
"version": "v7.2.0", "version": "v7.2.0",
@@ -9474,12 +9838,12 @@
], ],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": {}, "stability-flags": [],
"prefer-stable": true, "prefer-stable": true,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": "^8.2" "php": "^8.2"
}, },
"platform-dev": {}, "platform-dev": [],
"plugin-api-version": "2.6.0" "plugin-api-version": "2.6.0"
} }

View File

@@ -0,0 +1,40 @@
<?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) {
$indexes = DB::select("SHOW INDEXES FROM task_assignments WHERE Key_name = 'task_assignments_email_unique'");
if (!empty($indexes)) {
$table->dropUnique('task_assignments_email_unique');
}
$indexes = DB::select("SHOW INDEXES FROM task_assignments WHERE Key_name = 'task_assignments_username_unique'");
if (!empty($indexes)) {
$table->dropUnique('task_assignments_username_unique');
}
$table->string('email')->nullable()->change();
$table->string('username')->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('task_assignments', function (Blueprint $table) {
//
});
}
};

View File

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

View File

@@ -0,0 +1,29 @@
<?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('import_datasources', function (Blueprint $table) {
$table->timestamp('start_time')->nullable();
$table->timestamp('finish_time')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('import_datasources', function (Blueprint $table) {
$table->dropColumn(['start_time', 'finish_time']);
});
}
};

View File

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

View File

@@ -31,7 +31,8 @@ class DatabaseSeeder extends Seeder
$this->call([ $this->call([
RoleSeeder::class, RoleSeeder::class,
MenuSeeder::class, MenuSeeder::class,
UsersRoleMenuSeeder::class UsersRoleMenuSeeder::class,
GlobalSettingSeeder::class,
]); ]);
} }
} }

View File

@@ -134,7 +134,7 @@ class MenuSeeder extends Seeder
], ],
[ [
"name" => "Reklame", "name" => "Reklame",
"url" => "web.advertisements.index", "url" => "web-advertisements.index",
"icon" => null, "icon" => null,
"sort_order" => 2, "sort_order" => 2,
], ],

View File

@@ -20,18 +20,20 @@ echo "🗄️ Running migrations..."
php artisan migrate --force php artisan migrate --force
echo "Running seeders..." echo "Running seeders..."
php artisan db:seed php artisan db:seed --force
echo "⚡ Optimizing application..." echo "⚡ Optimizing application..."
php artisan optimize:clear php artisan optimize:clear
echo "🔄 Restarting PHP service..." echo "🔄 Restarting PHP service..."
systemctl restart $PHP_VERSION-fpm systemctl reload $PHP_VERSION-fpm
echo "🔁 Restarting Supervisor queue workers..." echo "🔁 Restarting Supervisor queue workers..."
supervisorctl stop all php artisan queue:restart
supervisorctl reload
supervisorctl start all supervisorctl reread
supervisorctl update
supervisorctl restart all
php artisan up php artisan up
echo "🚀 Deployment completed successfully!" echo "🚀 Deployment completed successfully!"

View File

@@ -17,6 +17,8 @@ class BigdataResume {
async initEvents() { async initEvents() {
await this.initBigdataResumeTable(); await this.initBigdataResumeTable();
// this.handleSearch(); // this.handleSearch();
await this.handleExportPDF();
await this.handleExportToExcel();
} }
async initBigdataResumeTable() { async initBigdataResumeTable() {
@@ -25,6 +27,7 @@ class BigdataResume {
this.table = new Grid({ this.table = new Grid({
columns: [ columns: [
{ name: "ID" }, { name: "ID" },
{ name: "Year" },
{ name: "Jumlah Potensi" }, { name: "Jumlah Potensi" },
{ name: "Total Potensi" }, { name: "Total Potensi" },
{ name: "Jumlah Berkas Belum Terverifikasi" }, { name: "Jumlah Berkas Belum Terverifikasi" },
@@ -77,6 +80,7 @@ class BigdataResume {
then: (data) => { then: (data) => {
return data.data.map((item) => [ return data.data.map((item) => [
item.id, item.id,
item.year,
item.potention_count, item.potention_count,
addThousandSeparators(item.potention_sum), addThousandSeparators(item.potention_sum),
item.non_verified_count, item.non_verified_count,
@@ -114,6 +118,100 @@ class BigdataResume {
}); });
} }
async handleExportToExcel() {
const button = document.getElementById("btn-export-excel");
if (!button) {
console.error("Button not found: #btn-export-excel");
return;
}
let exportUrl = button.getAttribute("data-url");
button.addEventListener("click", async () => {
button.disabled = true;
try {
const response = await fetch(`${exportUrl}`, {
method: "GET",
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
});
if (!response.ok) {
console.error("Error fetching data:", response.statusText);
button.disabled = false;
return;
}
// Convert response to Blob and trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "laporan-pimpinan.xlsx";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Error fetching data:", error);
button.disabled = false;
return;
} finally {
button.disabled = false;
}
});
}
async handleExportPDF() {
const button = document.getElementById("btn-export-pdf");
if (!button) {
console.error("Button not found: #btn-export-pdf");
return;
}
let exportUrl = button.getAttribute("data-url");
button.addEventListener("click", async () => {
button.disabled = true;
try {
const response = await fetch(`${exportUrl}`, {
method: "GET",
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
});
if (!response.ok) {
console.error("Error fetching data:", response.statusText);
button.disabled = false;
return;
}
// Convert response to Blob and trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "laporan-pimpinan.pdf";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Error fetching data:", error);
button.disabled = false;
return;
} finally {
button.disabled = false;
}
});
}
handleSearch() { handleSearch() {
document.getElementById("search-btn").addEventListener("click", () => { document.getElementById("search-btn").addEventListener("click", () => {
let searchValue = document.getElementById("search-box").value; let searchValue = document.getElementById("search-box").value;

View File

@@ -3,107 +3,99 @@ import GlobalConfig, { addThousandSeparators } from "../global-config.js";
import ApexCharts from "apexcharts"; import ApexCharts from "apexcharts";
import "gridjs/dist/gridjs.umd.js"; import "gridjs/dist/gridjs.umd.js";
import GeneralTable from "../table-generator.js"; import GeneralTable from "../table-generator.js";
import InitDatePicker from "../utils/InitDatePicker.js";
var chart; var chart;
document.addEventListener("DOMContentLoaded", async function () {
await initChart();
const yearPicker = document.getElementById("yearPicker");
async function updateDataByYear(selectedYear) { class DashboardPBG {
// Target PAD Element async init() {
try {
new InitDatePicker(
"#datepicker-dashboard-pbg",
this.handleChangedDate.bind(this)
).init();
// Load initial data
this.updateData("latest");
} catch (error) {
console.error("Error initializing data:", error);
}
}
handleChangedDate(filterDate) {
if (!filterDate) return;
this.updateData(filterDate);
}
async updateData(filterDate) {
let resumeData = await this.getResume(filterDate);
if (!resumeData) return;
let targetPAD = resumeData.target_pad.sum;
const targetPadElement = document.getElementById("target-pad"); const targetPadElement = document.getElementById("target-pad");
if (!targetPadElement) return; targetPadElement.textContent = formatCurrency(targetPAD);
const targetPadValue = await getDataSettings("TARGET_PAD");
targetPadElement.textContent = formatCurrency(targetPadValue);
// Total Potensi Berkas
const totalPotensiBerkas = document.getElementById( const totalPotensiBerkas = document.getElementById(
"total-potensi-berkas" "total-potensi-berkas"
); );
if (!totalPotensiBerkas) return;
const totalPotensiBerkasValue = await getDataTotalPotensi(selectedYear);
totalPotensiBerkas.textContent = formatCurrency( totalPotensiBerkas.textContent = formatCurrency(
totalPotensiBerkasValue.totalData resumeData.total_potensi.sum
); );
// Total Berkas Terverifikasi
const totalBerkasTerverifikasi = document.getElementById( const totalBerkasTerverifikasi = document.getElementById(
"total-berkas-terverifikasi" "total-berkas-terverifikasi"
); );
if (!totalBerkasTerverifikasi) return;
const totalBerkasTerverifikasiValue = await getDataVerification(
selectedYear
);
totalBerkasTerverifikasi.textContent = formatCurrency( totalBerkasTerverifikasi.textContent = formatCurrency(
totalBerkasTerverifikasiValue.totalData resumeData.verified_document.sum
); );
// Total Kekurangan potensi
const totalKekuranganPotensi = document.getElementById( const totalKekuranganPotensi = document.getElementById(
"total-kekurangan-potensi" "total-kekurangan-potensi"
); );
if (!totalKekuranganPotensi) return;
const totalKekuranganPotensiValue =
new Big(targetPadValue) -
new Big(totalPotensiBerkasValue.totalData);
totalKekuranganPotensi.textContent = formatCurrency( totalKekuranganPotensi.textContent = formatCurrency(
totalKekuranganPotensiValue resumeData.kekurangan_potensi.sum
); );
// Total Potensi PBG dari tata ruang
const totalPotensiPBGTataRuang = document.getElementById( const totalPotensiPBGTataRuang = document.getElementById(
"total-potensi-pbd-tata-ruang" "total-potensi-pbd-tata-ruang"
); );
if (!totalPotensiPBGTataRuang) return; totalPotensiPBGTataRuang.textContent = "Rp.-";
const totalPotensiPBGTataRuangValue = await getDataSettings(
"TATA_RUANG"
);
totalPotensiPBGTataRuang.textContent = formatCurrency(
totalPotensiPBGTataRuangValue
);
// Total Berkas Belum terverifikasi
const totalBerkasBelumTerverifikasi = document.getElementById( const totalBerkasBelumTerverifikasi = document.getElementById(
"total-berkas-belum-terverifikasi" "total-berkas-belum-terverifikasi"
); );
if (!totalBerkasBelumTerverifikasi) return;
const totalBerkasBelumTerverifikasiValue = await getDataNonVerification(
selectedYear
);
const totalBerkasBelumTerverifikasiCount =
totalBerkasBelumTerverifikasiValue.countData;
totalBerkasBelumTerverifikasi.textContent = formatCurrency( totalBerkasBelumTerverifikasi.textContent = formatCurrency(
totalBerkasBelumTerverifikasiValue.totalData resumeData.non_verified_document.sum
); );
const totalRealisasiTerbitPBG = document.getElementById(
"realisasi-terbit-pbg"
);
totalRealisasiTerbitPBG.textContent = formatCurrency(
resumeData.realisasi_terbit.sum
);
const totalProsesDinasTeknis = document.getElementById(
"processing-technical-services"
);
totalProsesDinasTeknis.textContent = formatCurrency(
resumeData.proses_dinas_teknis.sum
);
await this.initPieChart(resumeData);
}
async initPieChart(data) {
// Total Berkas Usaha // Total Berkas Usaha
const totalBerkasUsahaValue = await getDataBusiness(selectedYear); const totalBerkasUsahaTotalData = data.verified_document.sum;
const totalBerkasUsahaCount = totalBerkasUsahaValue.countData;
const totalBerkasUsahaTotalData = totalBerkasUsahaValue.totalData;
// Total Berkas Non Usaha // Total Berkas Non Usaha
const totalBerkasNonUsahaValue = await getDataNonBusiness(selectedYear); const totalBerkasNonUsahaTotalData = data.non_verified_document.sum;
const totalBerkasNonUsahaCount = totalBerkasNonUsahaValue.countData;
const totalBerkasNonUsahaTotalData = totalBerkasNonUsahaValue.totalData;
// Pie Chart Section // Pie Chart Section
let persenUsaha = let persenUsaha = data.verified_document.percentage;
totalBerkasBelumTerverifikasiCount > 0
? (
(totalBerkasUsahaCount /
totalBerkasBelumTerverifikasiCount) *
100
).toFixed(2)
: "0";
let persenNonUsaha = let persenNonUsaha = data.non_verified_document.percentage;
totalBerkasBelumTerverifikasiCount > 0
? (
(totalBerkasNonUsahaCount /
totalBerkasBelumTerverifikasiCount) *
100
).toFixed(2)
: "0";
const dataSeriesPieChart = [ const dataSeriesPieChart = [
Number(persenUsaha), Number(persenUsaha),
@@ -123,7 +115,41 @@ document.addEventListener("DOMContentLoaded", async function () {
).textContent = persenUsaha + "%"; ).textContent = persenUsaha + "%";
updatePieChart(dataSeriesPieChart, labelsPieChart); updatePieChart(dataSeriesPieChart, labelsPieChart);
}
async getResume(filterByDate) {
try {
const response = await fetch(
`${GlobalConfig.apiHost}/api/bigdata-resume?filterByDate=${filterByDate}`,
{
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 null;
}
}
}
document.addEventListener("DOMContentLoaded", async function (e) {
await new DashboardPBG().init();
await initChart();
async function updateDataByYear() {
// Load all Tourism location // Load all Tourism location
const allLocation = await getAllLocation(); const allLocation = await getAllLocation();
console.log(allLocation); console.log(allLocation);
@@ -159,42 +185,6 @@ document.addEventListener("DOMContentLoaded", async function () {
.bindTooltip(loc.name, { permanent: false, direction: "top" }); // Tooltip saat di-hover .bindTooltip(loc.name, { permanent: false, direction: "top" }); // Tooltip saat di-hover
}); });
// Realisasi terbit PBG
const totalRealisasiTerbitPBG = document.getElementById(
"realisasi-terbit-pbg"
);
if (!totalRealisasiTerbitPBG) return;
const totalRealisasiTerbitPBGValue = await getDataSettings(
"REALISASI_TERBIT_PBG_SUM"
);
totalRealisasiTerbitPBG.textContent = formatCurrency(
totalRealisasiTerbitPBGValue
);
// Menunggu Klik DPMPTSP
const totalMenungguKlikDpmptsp = document.getElementById(
"waiting-click-dpmptsp"
);
if (!totalMenungguKlikDpmptsp) return;
const totalMenungguKlikDpmptspValue = await getDataSettings(
"MENUNGGU_KLIK_DPMPTSP_SUM"
);
totalMenungguKlikDpmptsp.textContent = formatCurrency(
totalMenungguKlikDpmptspValue
);
// Proses Dinas Teknis
const totalProsesDinasTeknis = document.getElementById(
"processing-technical-services"
);
if (!totalProsesDinasTeknis) return;
const totalProsesDinasTeknisValue = await getDataSettings(
"PROSES_DINAS_TEKNIS_SUM"
);
totalProsesDinasTeknis.textContent = formatCurrency(
totalProsesDinasTeknisValue
);
// Load Tabel Baru di Update // Load Tabel Baru di Update
const tableLastUpdated = new GeneralTable( const tableLastUpdated = new GeneralTable(
"pbg-filter-by-updated-at", "pbg-filter-by-updated-at",
@@ -255,187 +245,8 @@ document.addEventListener("DOMContentLoaded", async function () {
).hidden = true; ).hidden = true;
} }
await updateDataByYear(yearPicker.value); await updateDataByYear();
yearPicker.addEventListener("change", async function () {
console.log("event change dropdown");
await updateDataByYear(yearPicker.value);
}); });
});
async function 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 ?? 0; // Pastikan tidak error jika data kosong
} catch (error) {
console.error("Error fetching data:", error);
return 0;
}
}
async function 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 {
totalData: data.data.total,
};
} catch (error) {
console.error("Error fetching chart data:", error);
return null;
}
}
async function getDataVerification(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 {
totalData: data.data.total,
};
} catch (error) {
console.error("Error fetching chart data:", error);
return 0;
}
}
async function getDataNonVerification(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 {
countData: data.data.count,
totalData: data.data.total,
};
} catch (error) {
console.error("Error fetching chart data:", error);
return 0;
}
}
async function 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 {
countData: data.data.count,
totalData: data.data.total,
};
} catch (error) {
console.error("Error fetching chart data:", error);
return 0;
}
}
async function 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 {
countData: data.data.count,
totalData: data.data.total,
};
} catch (error) {
console.error("Error fetching chart data:", error);
return 0;
}
}
async function getAllLocation() { async function getAllLocation() {
try { try {

View File

@@ -41,8 +41,8 @@ class GoogleSheets {
tableContainer.innerHTML = ""; tableContainer.innerHTML = "";
// Get user permissions from data attributes // Get user permissions from data attributes
let canUpdate = tableContainer.getAttribute("data-updater") === "1"; // let canUpdate = tableContainer.getAttribute("data-updater") === "1";
let canDelete = tableContainer.getAttribute("data-destroyer") === "1"; // let canDelete = tableContainer.getAttribute("data-destroyer") === "1";
this.table = new Grid({ this.table = new Grid({
columns: [ columns: [
@@ -65,25 +65,25 @@ class GoogleSheets {
</a> </a>
`; `;
if (canUpdate) { // if (canUpdate) {
buttons += ` // buttons += `
<a href="#" class="btn btn-yellow btn-sm d-inline-flex align-items-center justify-content-center"> // <a href="#" class="btn btn-yellow btn-sm d-inline-flex align-items-center justify-content-center">
<i class='bx bx-edit'></i> // <i class='bx bx-edit'></i>
</a> // </a>
`; // `;
} // }
if (canDelete) { // if (canDelete) {
buttons += ` // buttons += `
<button data-id="${cell}" class="btn btn-sm btn-red btn-delete-google-sheet d-inline-flex align-items-center justify-content-center"> // <button data-id="${cell}" class="btn btn-sm btn-red btn-delete-google-sheet d-inline-flex align-items-center justify-content-center">
<i class='bx bxs-trash'></i> // <i class='bx bxs-trash'></i>
</button> // </button>
`; // `;
} // }
if (!canUpdate && !canDelete) { // if (!canUpdate && !canDelete) {
buttons = `<span class="text-muted">No Privilege</span>`; // buttons = `<span class="text-muted">No Privilege</span>`;
} // }
return gridjs.html( return gridjs.html(
`<div class="d-flex justify-content-center gap-2">${buttons}</div>` `<div class="d-flex justify-content-center gap-2">${buttons}</div>`

View File

@@ -2,10 +2,25 @@ import { Grid } from "gridjs/dist/gridjs.umd.js";
import gridjs from "gridjs/dist/gridjs.umd.js"; import gridjs from "gridjs/dist/gridjs.umd.js";
import "gridjs/dist/gridjs.umd.js"; import "gridjs/dist/gridjs.umd.js";
import GlobalConfig from "../../global-config"; import GlobalConfig from "../../global-config";
import Swal from "sweetalert2";
class UsersTable { class UsersTable {
init() { constructor() {
this.toastMessage = document.getElementById("toast-message");
this.toastElement = document.getElementById("toastNotification");
this.toast = new bootstrap.Toast(this.toastElement);
this.table = null;
this.initTableUsers(); this.initTableUsers();
this.initEvents();
}
initEvents() {
document.body.addEventListener("click", async (event) => {
const deleteButton = event.target.closest(".btn-delete-users");
if (deleteButton) {
event.preventDefault();
await this.handleDelete(deleteButton);
}
});
} }
initTableUsers() { initTableUsers() {
@@ -14,7 +29,8 @@ class UsersTable {
tableContainer.innerHTML = ""; tableContainer.innerHTML = "";
let canUpdate = tableContainer.getAttribute("data-updater") === "1"; let canUpdate = tableContainer.getAttribute("data-updater") === "1";
new Grid({ let canDestroy = tableContainer.getAttribute("data-destroyer") === "1";
this.table = new Grid({
columns: [ columns: [
"ID", "ID",
"Name", "Name",
@@ -26,18 +42,28 @@ class UsersTable {
{ {
name: "Action", name: "Action",
formatter: (cell) => { formatter: (cell) => {
if (!canUpdate) { if (!canUpdate && !canDestroy) {
return gridjs.html( return gridjs.html(
`<span class="text-muted">No Privilege</span>` `<span class="text-muted">No Privilege</span>`
); );
} }
return gridjs.html(`
<div class="d-flex justify-content-center"> let buttons = `<div class="d-flex justify-content-center align-items-center gap-2">`;
buttons += `
<a href="/master/users/${cell}/edit?menu_id=${menuId}" class="btn btn-yellow btn-sm d-inline-flex align-items-center justify-content-center"> <a href="/master/users/${cell}/edit?menu_id=${menuId}" class="btn btn-yellow btn-sm d-inline-flex align-items-center justify-content-center">
<i class='bx bx-edit'></i> <i class='bx bx-edit'></i>
</a> </a>
</div> `;
`); buttons += `
<button data-id="${cell}" class="btn btn-sm btn-red btn-delete-users d-inline-flex align-items-center justify-content-center">
<i class='bx bxs-trash'></i>
</button>
`;
buttons += `</div>`;
return gridjs.html(buttons);
}, },
}, },
], ],
@@ -67,7 +93,6 @@ class UsersTable {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
then: (data) => { then: (data) => {
console.log(data.data);
return data.data.map((item) => [ return data.data.map((item) => [
item.id, item.id,
item.name, item.name,
@@ -83,8 +108,62 @@ class UsersTable {
}, },
}).render(document.getElementById("table-users")); }).render(document.getElementById("table-users"));
} }
async handleDelete(button) {
const id = button.getAttribute("data-id");
const result = await Swal.fire({
title: "Are you sure?",
text: "You won't be able to revert this!",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
confirmButtonText: "Yes, delete it!",
});
if (result.isConfirmed) {
try {
let response = await fetch(
`${GlobalConfig.apiHost}/api/users/${id}`,
{
method: "DELETE",
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
},
}
);
if (response.ok) {
let result = await response.json();
this.toastMessage.innerText =
result.message || "Deleted successfully!";
this.toast.show();
// Refresh Grid.js table
if (typeof this.table !== "undefined") {
this.table.updateConfig({}).forceRender();
}
} else {
let error = await response.json();
console.error("Delete failed:", error);
this.toastMessage.innerText =
error.message || "Delete failed!";
this.toast.show();
}
} catch (error) {
console.error("Error deleting item:", error);
this.toastMessage.innerText = "An error occurred!";
this.toast.show();
}
}
}
} }
document.addEventListener("DOMContentLoaded", function (e) { document.addEventListener("DOMContentLoaded", function (e) {
new UsersTable().init(); new UsersTable();
}); });

View File

@@ -39,7 +39,7 @@ class Menus {
"Name", "Name",
"Url", "Url",
"Icon", "Icon",
"ParentID", "Parent Name",
"Sort Order", "Sort Order",
{ {
name: "Action", name: "Action",
@@ -97,16 +97,22 @@ class Menus {
.getAttribute("content")}`, .getAttribute("content")}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
then: (data) => then: (data) => {
data.data.map((item) => [ console.log("Full API Response:", data); // Log the full response
return data.data.map((item, index) => {
console.log(`Item ${index + 1}:`, item); // Log each item
return [
item.id, item.id,
item.name, item.name,
item.url, item.url,
item.icon, item.icon,
item.parent_id, item.parent?.name,
item.sort_order, item.sort_order,
item.id, item.id,
]), ];
});
},
total: (data) => data.total, total: (data) => data.total,
}, },
}).render(tableContainer); }).render(tableContainer);

View File

@@ -11,8 +11,15 @@ class PaymentRecaps {
this.toastElement = document.getElementById("toastNotification"); this.toastElement = document.getElementById("toastNotification");
this.toast = new bootstrap.Toast(this.toastElement); this.toast = new bootstrap.Toast(this.toastElement);
this.table = null; this.table = null;
this.startDate = undefined;
this.endDate = undefined;
}
init() {
this.initTablePaymentRecaps(); this.initTablePaymentRecaps();
this.initFilterDatepicker(); this.initFilterDatepicker();
this.handleFilterBtn();
this.handleExportPDF();
this.handleExportToExcel();
} }
initFilterDatepicker() { initFilterDatepicker() {
new InitDatePicker( new InitDatePicker(
@@ -20,8 +27,13 @@ class PaymentRecaps {
this.handleChangeFilterDate.bind(this) this.handleChangeFilterDate.bind(this)
).init(); ).init();
} }
handleChangeFilterDate(strDate) { handleChangeFilterDate(filterDate) {
console.log("filter date : ", strDate); this.startDate = moment(filterDate, "YYYY-MM-DD")
.startOf("day")
.format("YYYY-MM-DD");
this.endDate = moment(filterDate, "YYYY-MM-DD")
.endOf("day")
.format("YYYY-MM-DD");
} }
formatCategory(category) { formatCategory(category) {
const categoryMap = { const categoryMap = {
@@ -41,6 +53,44 @@ class PaymentRecaps {
initTablePaymentRecaps() { initTablePaymentRecaps() {
let tableContainer = document.getElementById("table-payment-recaps"); let tableContainer = document.getElementById("table-payment-recaps");
// Fetch data from the server
fetch(
`${GlobalConfig.apiHost}/api/payment-recaps?start_date=${
this.startDate || ""
}&end_date=${this.endDate || ""}`,
{
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
},
}
)
.then((response) => response.json())
.then((data) => {
if (!data || !Array.isArray(data.data)) {
console.error("Error: Data is not an array", data);
return;
}
let formattedData = data.data.map((item) => [
this.formatCategory(item.category ?? "Unknown"),
addThousandSeparators(Number(item.nominal).toString() || 0),
moment(item.created_at).isValid()
? moment(item.created_at).format("YYYY-MM-DD H:mm:ss")
: "-",
]);
// 🔥 If the table already exists, update it instead of re-creating
if (this.table) {
this.table
.updateConfig({
data: formattedData.length > 0 ? formattedData : [],
})
.forceRender();
} else {
// 🔹 First-time initialization
this.table = new Grid({ this.table = new Grid({
columns: [ columns: [
{ name: "Kategori", data: (row) => row[0] }, { name: "Kategori", data: (row) => row[0] },
@@ -48,57 +98,163 @@ class PaymentRecaps {
{ {
name: "Created", name: "Created",
data: (row) => row[2], data: (row) => row[2],
attributes: { style: "width: 200px; white-space: nowrap;" }, attributes: {
style: "width: 200px; white-space: nowrap;",
},
}, },
], ],
pagination: { pagination: {
limit: 10, limit: 50,
server: {
url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${
page + 1
}`,
},
}, },
sort: true, sort: true,
server: { data: formattedData.length > 0 ? formattedData : [],
url: `${GlobalConfig.apiHost}/api/payment-recaps`,
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
},
then: (response) => {
console.log("API Response:", response); // Debugging
if (!response.data || !Array.isArray(response.data)) {
console.error(
"Error: Data is not an array",
response.data
);
return [];
}
return response.data.map((item) => [
this.formatCategory(item.category ?? "Unknown"), // Ensure category is not null
addThousandSeparators(
Number(item.nominal).toString() || 0
), // Ensure nominal is a valid number
moment(item.created_at).isValid()
? moment(item.created_at).format(
"YYYY-MM-DD H:mm:ss"
)
: "-", // Handle invalid dates
]);
},
total: (response) => response.pagination?.total || 0,
},
width: "auto", width: "auto",
fixedHeader: true, fixedHeader: true,
}).render(tableContainer); }).render(tableContainer);
} }
})
.catch((error) => console.error("Error fetching data:", error));
}
async handleFilterBtn() {
const filterBtn = document.getElementById("btnFilterData");
if (!filterBtn) {
console.error("Button not found: #btnFilterData");
return;
}
filterBtn.addEventListener("click", async () => {
if (!this.startDate || !this.endDate) {
console.log("No date filter applied, using default data");
} else {
console.log(
`Filtering with dates: ${this.startDate} - ${this.endDate}`
);
}
// Reinitialize table with updated filters
this.initTablePaymentRecaps();
});
}
async handleExportToExcel() {
const button = document.getElementById("btn-export-excel");
if (!button) {
console.error("Button not found: #btn-export-excel");
return;
}
button.addEventListener("click", async () => {
button.disabled = true;
let exportUrl = new URL(button.getAttribute("data-url"));
if (this.startDate) {
exportUrl.searchParams.append("start_date", this.startDate);
} else {
console.warn("⚠️ start_date is missing");
}
if (this.endDate) {
exportUrl.searchParams.append("end_date", this.endDate);
} else {
console.warn("⚠️ end_date is missing");
}
// Final check
console.log("Final Export URL:", exportUrl.toString());
try {
const response = await fetch(`${exportUrl}`, {
method: "GET",
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
});
if (!response.ok) {
console.error("Error fetching data:", response.statusText);
button.disabled = false;
return;
}
// Convert response to Blob and trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "rekap-pembayaran.xlsx";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Error fetching data:", error);
button.disabled = false;
return;
} finally {
button.disabled = false;
}
});
}
async handleExportPDF() {
const button = document.getElementById("btn-export-pdf");
if (!button) {
console.error("Button not found: #btn-export-pdf");
return;
}
button.addEventListener("click", async () => {
button.disabled = true;
let exportUrl = new URL(button.getAttribute("data-url"));
if (this.startDate) {
exportUrl.searchParams.append("start_date", this.startDate);
} else {
console.warn("⚠️ start_date is missing");
}
if (this.endDate) {
exportUrl.searchParams.append("end_date", this.endDate);
} else {
console.warn("⚠️ end_date is missing");
}
try {
const response = await fetch(`${exportUrl}`, {
method: "GET",
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
});
if (!response.ok) {
console.error("Error fetching data:", response.statusText);
button.disabled = false;
return;
}
// Convert response to Blob and trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "rekap-pembayaran.pdf";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Error fetching data:", error);
button.disabled = false;
return;
} finally {
button.disabled = false;
}
});
}
} }
document.addEventListener("DOMContentLoaded", function (e) { document.addEventListener("DOMContentLoaded", function (e) {
new PaymentRecaps(); new PaymentRecaps().init();
}); });

View File

@@ -2,10 +2,22 @@ import { Grid } from "gridjs/dist/gridjs.umd.js";
import "gridjs/dist/gridjs.umd.js"; import "gridjs/dist/gridjs.umd.js";
import gridjs from "gridjs/dist/gridjs.umd.js"; import gridjs from "gridjs/dist/gridjs.umd.js";
import GlobalConfig from "../global-config"; import GlobalConfig from "../global-config";
import flatpickr from "flatpickr";
import "flatpickr/dist/flatpickr.min.css";
class PbgTaskAssignments { class PbgTaskAssignments {
init() { init() {
this.initTablePbgTaskAssignments(); this.initTablePbgTaskAssignments();
this.handleUpdateData();
this.initDatePicker();
}
initDatePicker() {
let element = document.getElementById("datepicker_due_date");
flatpickr(element, {
dateFormat: "Y-m-d",
minDate: new Date(),
});
} }
initTablePbgTaskAssignments() { initTablePbgTaskAssignments() {
@@ -62,6 +74,65 @@ class PbgTaskAssignments {
}, },
}).render(tableContainer); }).render(tableContainer);
} }
handleUpdateData() {
const button = document.getElementById("btnUpdatePbgTask");
const form = document.getElementById("formUpdatePbgTask");
const toastNotification = document.getElementById("toastNotification");
const toast = new bootstrap.Toast(toastNotification);
button.addEventListener("click", function (event) {
event.preventDefault();
let submitButton = this;
let spinner = document.getElementById("spinner");
submitButton.disabled = true;
spinner.classList.remove("d-none");
const formData = new FormData(form);
const formObject = {};
formData.forEach((value, key) => {
formObject[key] = value;
});
fetch(form.action, {
method: "PUT", // Ensure your Laravel route is set to accept PUT requests
body: JSON.stringify(formObject), // Convert form data to JSON
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
"X-CSRF-TOKEN": document.querySelector(
'meta[name="csrf-token"]'
).content, // For Laravel security
},
})
.then((response) => {
if (!response.ok) {
return response.json().then((err) => {
throw new Error(
err.message || "Something went wrong"
);
});
}
return response.json();
})
.then((data) => {
document.getElementById("toast-message").innerText =
data.message;
toast.show();
submitButton.disabled = false;
spinner.classList.add("d-none");
})
.catch((error) => {
console.error("Error updating task:", error);
document.getElementById("toast-message").innerText =
error.message;
toast.show();
submitButton.disabled = false;
spinner.classList.add("d-none");
});
});
}
} }
document.addEventListener("DOMContentLoaded", function (e) { document.addEventListener("DOMContentLoaded", function (e) {

View File

@@ -7,6 +7,8 @@ class ReportPaymentRecaps {
constructor() { constructor() {
this.table = null; this.table = null;
this.initTableReportPaymentRecaps(); this.initTableReportPaymentRecaps();
this.handleExportPDF();
this.handleExportToExcel();
} }
initTableReportPaymentRecaps() { initTableReportPaymentRecaps() {
let tableContainer = document.getElementById( let tableContainer = document.getElementById(
@@ -63,6 +65,100 @@ class ReportPaymentRecaps {
fixedHeader: true, fixedHeader: true,
}).render(tableContainer); }).render(tableContainer);
} }
async handleExportToExcel() {
const button = document.getElementById("btn-export-excel");
if (!button) {
console.error("Button not found: #btn-export-excel");
return;
}
let exportUrl = button.getAttribute("data-url");
button.addEventListener("click", async () => {
button.disabled = true;
try {
const response = await fetch(`${exportUrl}`, {
method: "GET",
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
});
if (!response.ok) {
console.error("Error fetching data:", response.statusText);
button.disabled = false;
return;
}
// Convert response to Blob and trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "laporan-rekap-data-pembayaran.xlsx";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Error fetching data:", error);
button.disabled = false;
return;
} finally {
button.disabled = false;
}
});
}
async handleExportPDF() {
const button = document.getElementById("btn-export-pdf");
if (!button) {
console.error("Button not found: #btn-export-pdf");
return;
}
let exportUrl = button.getAttribute("data-url");
button.addEventListener("click", async () => {
button.disabled = true;
try {
const response = await fetch(`${exportUrl}`, {
method: "GET",
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
});
if (!response.ok) {
console.error("Error fetching data:", response.statusText);
button.disabled = false;
return;
}
// Convert response to Blob and trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "laporan-rekap-data-pembayaran.pdf";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Error fetching data:", error);
button.disabled = false;
return;
} finally {
button.disabled = false;
}
});
}
} }
document.addEventListener("DOMContentLoaded", function (e) { document.addEventListener("DOMContentLoaded", function (e) {
new ReportPaymentRecaps(); new ReportPaymentRecaps();

View File

@@ -7,6 +7,8 @@ class ReportPbgPTSP {
constructor() { constructor() {
this.table = null; this.table = null;
this.initTableReportPbgPTSP(); this.initTableReportPbgPTSP();
this.handleExportToExcel();
this.handleExportPDF();
} }
initTableReportPbgPTSP() { initTableReportPbgPTSP() {
let tableContainer = document.getElementById("table-report-pbg-ptsp"); let tableContainer = document.getElementById("table-report-pbg-ptsp");
@@ -61,6 +63,99 @@ class ReportPbgPTSP {
fixedHeader: true, fixedHeader: true,
}).render(tableContainer); }).render(tableContainer);
} }
async handleExportToExcel() {
const button = document.getElementById("btn-export-excel");
if (!button) {
console.error("Button not found: #btn-export-excel");
return;
}
let exportUrl = button.getAttribute("data-url");
button.addEventListener("click", async () => {
button.disabled = true;
try {
const response = await fetch(`${exportUrl}`, {
method: "GET",
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
});
if (!response.ok) {
console.error("Error fetching data:", response.statusText);
button.disabled = false;
return;
}
// Convert response to Blob and trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "laporan-ptsp.xlsx";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Error fetching data:", error);
button.disabled = false;
return;
} finally {
button.disabled = false;
}
});
}
async handleExportPDF() {
const button = document.getElementById("btn-export-pdf");
if (!button) {
console.error("Button not found: #btn-export-pdf");
return;
}
let exportUrl = button.getAttribute("data-url");
button.addEventListener("click", async () => {
button.disabled = true;
try {
const response = await fetch(`${exportUrl}`, {
method: "GET",
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
});
if (!response.ok) {
console.error("Error fetching data:", response.statusText);
button.disabled = false;
return;
}
// Convert response to Blob and trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "laporan-ptsp.pdf";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Error fetching data:", error);
button.disabled = false;
return;
} finally {
button.disabled = false;
}
});
}
} }
document.addEventListener("DOMContentLoaded", function (e) { document.addEventListener("DOMContentLoaded", function (e) {
new ReportPbgPTSP(); new ReportPbgPTSP();

View File

@@ -3,14 +3,121 @@ import "gridjs/dist/gridjs.umd.js";
// Mengambil data dari input dengan id="business_type_counts" // Mengambil data dari input dengan id="business_type_counts"
const businessTypeCountsElement = document.getElementById("tourism_based_KBLI"); const businessTypeCountsElement = document.getElementById("tourism_based_KBLI");
console.log(businessTypeCountsElement);
const businessTypeCounts = JSON.parse(businessTypeCountsElement.value); // Cek apakah data sudah terbawa dengan benar const businessTypeCounts = JSON.parse(businessTypeCountsElement.value); // Cek apakah data sudah terbawa dengan benar
// Membuat Grid.js instance // Membuat Grid.js instance
new gridjs.Grid({ new gridjs.Grid({
columns: ["Jenis Bisnis Pariwisata", "Jumlah Total"], // Nama kolom columns: ["Jenis Bisnis Pariwisata", "Jumlah Total"], // Nama kolom
data: businessTypeCounts.map(item => [item.kbli_title, item.total_records]), // Mengubah data untuk Grid.js data: businessTypeCounts.map((item) => [
item.kbli_title,
item.total_records,
]), // Mengubah data untuk Grid.js
search: true, // Menambahkan fitur pencarian search: true, // Menambahkan fitur pencarian
pagination: true, // Menambahkan fitur pagination pagination: true, // Menambahkan fitur pagination
sort: true, // Menambahkan fitur sorting sort: true, // Menambahkan fitur sorting
}).render(document.getElementById("tourisms-report-data-table")); }).render(document.getElementById("tourisms-report-data-table"));
class TourismReport {
init() {
this.handleExportToExcel();
this.handleExportPDF();
}
async handleExportToExcel() {
const button = document.getElementById("btn-export-excel");
if (!button) {
console.error("Button not found: #btn-export-excel");
return;
}
let exportUrl = button.getAttribute("data-url");
button.addEventListener("click", async () => {
button.disabled = true;
try {
const response = await fetch(`${exportUrl}`, {
method: "GET",
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
});
if (!response.ok) {
console.error("Error fetching data:", response.statusText);
button.disabled = false;
return;
}
// Convert response to Blob and trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "laporan-pariwisata.xlsx";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Error fetching data:", error);
button.disabled = false;
return;
} finally {
button.disabled = false;
}
});
}
async handleExportPDF() {
const button = document.getElementById("btn-export-pdf");
if (!button) {
console.error("Button not found: #btn-export-pdf");
return;
}
let exportUrl = button.getAttribute("data-url");
button.addEventListener("click", async () => {
button.disabled = true;
try {
const response = await fetch(`${exportUrl}`, {
method: "GET",
credentials: "include",
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
});
if (!response.ok) {
console.error("Error fetching data:", response.statusText);
button.disabled = false;
return;
}
// Convert response to Blob and trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "laporan-pariwisata.pdf";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Error fetching data:", error);
button.disabled = false;
return;
} finally {
button.disabled = false;
}
});
}
}
document.addEventListener("DOMContentLoaded", function () {
new TourismReport().init();
});

View File

@@ -56,7 +56,7 @@ class Roles {
if (canDelete) { if (canDelete) {
buttons += ` buttons += `
<button data-id="${cell}" class="btn btn-sm btn-red btn-delete-role d-inline-flex align-items-center justify-content-center"> <button data-id="${cell}" class="btn btn-sm btn-red btn-delete-role d-none d-inline-flex align-items-center justify-content-center">
<i class='bx bxs-trash'></i> <i class='bx bxs-trash'></i>
</button> </button>
`; `;

View File

@@ -19,7 +19,29 @@ class SyncronizeTask {
"table-import-datasources" "table-import-datasources"
); );
this.table = new gridjs.Grid({ this.table = new gridjs.Grid({
columns: ["ID", "Message", "Response", "Status", "Created"], columns: [
"ID",
"Message",
"Response",
"Status",
"Started",
"Duration",
"Finished",
"Created",
{
name: "Action",
formatter: (cell) => {
if (cell.status === "failed") {
return gridjs.html(`
<button data-id="${cell.id}" class="btn btn-sm btn-warning d-flex align-items-center gap-1 btn-retry">
<iconify-icon icon="mingcute:refresh-3-line" width="15" height="15"></iconify-icon>
<span>Retry</span>
</button>
`);
}
},
},
],
search: { search: {
server: { server: {
url: (prev, keyword) => `${prev}?search=${keyword}`, url: (prev, keyword) => `${prev}?search=${keyword}`,
@@ -49,11 +71,24 @@ class SyncronizeTask {
item.message, item.message,
item.response_body, item.response_body,
item.status, item.status,
item.start_time,
item.duration,
item.finish_time,
item.created_at, item.created_at,
item,
]), ]),
total: (data) => data.meta.total, total: (data) => data.meta.total,
}, },
}).render(tableContainer); }).render(tableContainer);
tableContainer.addEventListener("click", (event) => {
let btn = event.target.closest(".btn-retry");
if (btn) {
const id = btn.getAttribute("data-id");
btn.disabled = true;
this.handleRetrySync(id, btn);
}
});
} }
handleSubmitSync() { handleSubmitSync() {
const button = document.getElementById("btn-sync-submit"); const button = document.getElementById("btn-sync-submit");
@@ -105,6 +140,48 @@ class SyncronizeTask {
}); });
} }
handleRetrySync(id, btn) {
const apiToken = document
.querySelector('meta[name="api-token"]')
.getAttribute("content");
fetch(`${GlobalConfig.apiHost}/api/retry-scraping/${id}`, {
method: "GET",
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json",
},
})
.then(async (response) => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log("API Response:", data); // Debugging
// Show success message
const message =
data?.data?.message ||
data?.message ||
"Synchronization successful!";
this.toastMessage.innerText = message;
this.toast.show();
})
.catch((err) => {
console.error("Fetch error:", err);
// Show error message
this.toastMessage.innerText =
err.message ||
"Failed to synchronize, something went wrong!";
this.toast.show();
// Re-enable button on failure
btn.disabled = false;
});
}
handleSyncClick() { handleSyncClick() {
const button = document.getElementById("btn-sync-submit"); const button = document.getElementById("btn-sync-submit");
const spinner = document.getElementById("spinner"); const spinner = document.getElementById("spinner");
@@ -148,8 +225,11 @@ class SyncronizeTask {
}) })
.catch((err) => { .catch((err) => {
console.error("Fetch error:", err); console.error("Fetch error:", err);
alert("An error occurred during synchronization" + err.message); this.toastMessage.innerText =
err.message || "Failed to syncronize something wrong!";
this.toast.show();
button.disabled = false; button.disabled = false;
spinner.classList.add("d-none");
}); });
} }
} }

View File

@@ -30,7 +30,7 @@ class="authentication-bg"
@if (sizeof($errors) > 0) @if (sizeof($errors) > 0)
@foreach ($errors->all() as $error) @foreach ($errors->all() as $error)
<p class="text-red-600 mb-3">{{ $error }}</p> <p class="text-red mb-3">{{ $error }}</p>
@endforeach @endforeach
@endif @endif
@@ -39,6 +39,7 @@ class="authentication-bg"
<input type="email" class="form-control" id="email" name="email" placeholder="Enter your email"> <input type="email" class="form-control" id="email" name="email" placeholder="Enter your email">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" placeholder="Enter your password"> <input type="password" class="form-control" id="password" name="password" placeholder="Enter your password">
</div> </div>
<div class="d-grid"> <div class="d-grid">

View File

@@ -13,6 +13,19 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card w-100 h-100"> <div class="card w-100 h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Laporan Pimpinan</h5>
<div class="d-flex gap-2">
<button class="btn btn-sm bg-black text-white d-flex align-items-center content-center gap-2" id="btn-export-excel" data-url="{{ route('api.report-director.excel') }}">
<span>.xlsx</span>
<iconify-icon icon="mingcute:file-export-line" width="20" height="20" class="d-flex align-items-center"></iconify-icon>
</button>
<button class="btn btn-sm bg-black text-white d-flex align-items-center content-center gap-2" id="btn-export-pdf" data-url="{{ route('api.report-director.pdf') }}">
<span>.pdf</span>
<iconify-icon icon="mingcute:file-export-line" width="20" height="20" class="d-flex align-items-center"></iconify-icon>
</button>
</div>
</div>
<div class="card-body"> <div class="card-body">
<div id="table-bigdata-resumes"></div> <div id="table-bigdata-resumes"></div>
</div> </div>

View File

@@ -12,11 +12,7 @@
<div class="card mb-3 mb-xl-0"> <div class="card mb-3 mb-xl-0">
<div class="card-title mt-3"> <div class="card-title mt-3">
<div class="d-flex flex-sm-nowrap flex-wrap justify-content-end gap-2 me-3"> <div class="d-flex flex-sm-nowrap flex-wrap justify-content-end gap-2 me-3">
<select class="form-select w-25 w-sm-auto" id="yearPicker" name="year" style="min-width: 100px;"> <input type="text" class="form-control mt-2" style="max-width: 125px;" id="datepicker-dashboard-pbg" placeholder="Filter Date" />
@for ($i = date('Y'); $i > date('Y') - 5; $i--)
<option value="{{ $i }}" {{ $i == date('Y') ? 'selected' : '' }}>{{ $i }}</option>
@endfor
</select>
</div> </div>
</div> </div>
@@ -218,7 +214,7 @@
<div class="row"> <div class="row">
<!-- Card 1 --> <!-- Card 1 -->
<div class="col-md-12 col-lg-12 col-xl-4"> <div class="col-md-12 col-lg-12 col-xl-6">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
@@ -240,7 +236,7 @@
</div> </div>
<!-- Card 2 --> <!-- Card 2 -->
<div class="col-md-12 col-lg-12 col-xl-4"> <div class="col-md-12 col-lg-12 col-xl-4 d-none">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
@@ -262,7 +258,7 @@
</div> </div>
<!-- Card 3 --> <!-- Card 3 -->
<div class="col-md-12 col-lg-12 col-xl-4"> <div class="col-md-12 col-lg-12 col-xl-6">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">

View File

@@ -15,9 +15,9 @@
<div class="card w-100"> <div class="card w-100">
<div class="card-body"> <div class="card-body">
<div class="d-flex flex-wrap justify-content-end align-items-center mb-2"> <div class="d-flex flex-wrap justify-content-end align-items-center mb-2">
@if ($user_menu_permission['allow_create']) <!-- @if ($user_menu_permission['allow_create'])
<a href="#" class="btn btn-success btn-sm d-block d-sm-inline w-auto">Create</a> <a href="#" class="btn btn-success btn-sm d-block d-sm-inline w-auto">Create</a>
@endif @endif -->
</div> </div>
<div id="table-data-google-sheets" <div id="table-data-google-sheets"
data-updater="{{ $user_menu_permission['allow_update'] }}" data-updater="{{ $user_menu_permission['allow_update'] }}"

View File

@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laporan Pimpinan</title>
<style>
body { font-size: 10px; } /* Reduce font size */
table { width: 100%; border-collapse: collapse; }
th, td { padding: 3px; font-size: 9px; border: 1px solid black;} /* Reduce padding */
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h2>Laporan Pimpinan</h2>
<table>
<thead>
<tr>
<th>Jumlah Potensi</th>
<th>Total Potensi</th>
<th>Jumlah Berkas Belum Terverifikasi</th>
<th>Total Berkas Belum Terverifikasi</th>
<th>Jumlah Berkas Terverifikasi</th>
<th>Total Berkas Terverifikasi</th>
<th>Jumlah Usaha</th>
<th>Total Usaha</th>
<th>Jumlah Non Usaha</th>
<th>Total Non Usaha</th>
<th>Jumlah Tata Ruang</th>
<th>Total Tata Ruang</th>
<th>Jumlah Menunggu Klik DPMPTSP</th>
<th>Total Menunggu Klik DPMPTSP</th>
<th>Jumlah Realisasi Terbit PBG</th>
<th>Total Realisasi Terbit PBG</th>
<th>Jumlah Proses Dinas Teknis</th>
<th>Total Proses Dinas Teknis</th>
<th>Tahun</th>
<th>Created</th>
</tr>
</thead>
<tbody>
@foreach($data as $item)
<tr>
<td>{{ $item->potention_count }}</td>
<td>{{ $item->potention_sum }}</td>
<td>{{ $item->non_verified_count }}</td>
<td>{{ $item->non_verified_sum }}</td>
<td>{{ $item->verified_count }}</td>
<td>{{ $item->verified_sum }}</td>
<td>{{ $item->business_count }}</td>
<td>{{ $item->business_sum }}</td>
<td>{{ $item->non_business_count }}</td>
<td>{{ $item->non_business_sum }}</td>
<td>{{ $item->spatial_count }}</td>
<td>{{ $item->spatial_sum }}</td>
<td>{{ $item->waiting_click_dpmptsp_count }}</td>
<td>{{ $item->waiting_click_dpmptsp_sum }}</td>
<td>{{ $item->issuance_realization_pbg_count }}</td>
<td>{{ $item->issuance_realization_pbg_sum }}</td>
<td>{{ $item->process_in_technical_office_count }}</td>
<td>{{ $item->process_in_technical_office_sum }}</td>
<td>{{ $item->year }}</td>
<td>{{ $item->created_at->format('Y-m-d') }}</td>
</tr>
@endforeach
</tbody>
</table>
</body>
</html>

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laporan Rekap Data Pembayaran</title>
<style>
body { font-family: Arial, sans-serif; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid black; padding: 8px; text-align: center; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h2>Laporan Rekap Data Pembayaran</h2>
<table>
<thead>
<tr>
<th>Kecamatan</th>
<th>Total</th>
</tr>
</thead>
<tbody>
@foreach($data as $item)
<tr>
<td>{{ $item->kecamatan }}</td>
<td>{{ $item->total }}</td>
</tr>
@endforeach
</tbody>
</table>
</body>
</html>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laporan Rekap Pembayaran</title>
<style>
body { font-family: Arial, sans-serif; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid black; padding: 8px; text-align: center; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h2>Laporan Rekap Pembayaran</h2>
<table>
<thead>
<tr>
<th>Kategori</th>
<th>Nominal</th>
<th>Created</th>
</tr>
</thead>
<tbody>
@foreach($data as $item)
<tr>
<td>{{ $item['category'] }}</td>
<td>{{ $item['nominal'] }}</td>
<td>{{ $item['created_at'] }}</td>
</tr>
@endforeach
</tbody>
</table>
</body>
</html>

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laporan PBG PTSP</title>
<style>
body { font-family: Arial, sans-serif; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid black; padding: 8px; text-align: center; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h2>Laporan PBG PTSP</h2>
<table>
<thead>
<tr>
<th>Status</th>
<th>Total</th>
</tr>
</thead>
<tbody>
@foreach($data as $item)
<tr>
<td>{{ $item->status_name }}</td>
<td>{{ $item->total }}</td>
</tr>
@endforeach
</tbody>
</table>
</body>
</html>

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laporan Pariwisata</title>
<style>
body { font-family: Arial, sans-serif; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid black; padding: 8px; text-align: center; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h2>Laporan Pariwisata</h2>
<table>
<thead>
<tr>
<th>Jenis Bisnis Pariwisata</th>
<th>Jumlah Total</th>
</tr>
</thead>
<tbody>
@foreach($data as $item)
<tr>
<td>{{ $item->kbli_title }}</td>
<td>{{ $item->total_records }}</td>
</tr>
@endforeach
</tbody>
</table>
</body>
</html>

View File

@@ -8,6 +8,8 @@
@include('layouts.partials/page-title', ['title' => 'Master', 'subtitle' => 'Users']) @include('layouts.partials/page-title', ['title' => 'Master', 'subtitle' => 'Users'])
<x-toast-notification />
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card w-100"> <div class="card w-100">

View File

@@ -13,6 +13,19 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card w-100"> <div class="card w-100">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Rekap Pembayaran</h5>
<div class="d-flex gap-2">
<button class="btn btn-sm bg-black text-white d-flex align-items-center content-center gap-2" id="btn-export-excel" data-url="{{ route('api.payment-recaps.excel') }}">
<span>.xlsx</span>
<iconify-icon icon="mingcute:file-export-line" width="20" height="20" class="d-flex align-items-center"></iconify-icon>
</button>
<button class="btn btn-sm bg-black text-white d-flex align-items-center content-center gap-2" id="btn-export-pdf" data-url="{{ route('api.payment-recaps.pdf') }}">
<span>.pdf</span>
<iconify-icon icon="mingcute:file-export-line" width="20" height="20" class="d-flex align-items-center"></iconify-icon>
</button>
</div>
</div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 d-flex justify-content-end align-items-center flex-wrap gap-2"> <div class="col-12 d-flex justify-content-end align-items-center flex-wrap gap-2">

View File

@@ -7,69 +7,93 @@
@section('content') @section('content')
@include('layouts.partials/page-title', ['title' => 'Data', 'subtitle' => 'PBG']) @include('layouts.partials/page-title', ['title' => 'Data', 'subtitle' => 'PBG'])
<x-toast-notification />
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<form action="{{ route('api.pbg-task.update', ['task_uuid' => $data->uuid]) }}" id="formUpdatePbgTask">
@csrf
<div class="row mb-3">
<div class="col-md-6">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" value="{{$data->name}}">
</div>
<div class="mb-3">
<label for="owner_name" class="form-label">Owner Name</label>
<input type="text" class="form-control" id="owner_name" name="owner_name" value="{{$data->owner_name}}">
</div>
<div class="mb-3">
<label for="application_type" class="form-label">Application Type Name</label>
<select name="application_type" class="form-select">
@foreach($applicationTypes as $key => $value)
<option value="{{ $key }}"
{{ (old('application_type', $data->application_type ?? '') == $key) ? 'selected' : '' }}>
{{ $value }}
</option>
@endforeach
</select>
</div>
<div class="mb-3">
<label for="condition" class="form-label">Condition</label>
<input type="text" class="form-control" id="condition" name="condition" value="{{$data->condition}}">
</div>
<div class="mb-3">
<label for="registration_number" class="form-label">Registration Number</label>
<input type="text" class="form-control" id="registration_number" name="registration_number" value="{{$data->registration_number}}">
</div>
<div class="mb-3">
<label for="document_number" class="form-label">Document Number</label>
<input type="text" class="form-control" id="document_number" name="document_number" value="{{$data->document_number}}">
</div>
<div class="mb-3">
<label for="status" class="form-label">Status Name</label>
<select name="status" class="form-select">
@foreach($statusOptions as $key => $value)
<option value="{{ $key }}" {{ old('status') == $key ? 'selected' : '' }}>
{{ $value }}
</option>
@endforeach
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="address" class="form-label">Address</label>
<input type="text" class="form-control" id="address" name="address" value="{{$data->address}}">
</div>
<div class="mb-3">
<label for="slf_status_name" class="form-label">SLF Status Name</label>
<input type="text" class="form-control" id="slf_status_name" name="slf_status_name" value="{{$data->slf_status_name}}">
</div>
<div class="mb-3">
<label for="function_type" class="form-label">Function Type</label>
<input type="text" class="form-control" id="function_type" name="function_type" value="{{$data->function_type}}">
</div>
<div class="mb-3">
<label for="consultation_type" class="form-label">Consultation Type</label>
<input type="text" class="form-control" id="consultation_type" name="consultation_type" value="{{$data->consultation_type}}">
</div>
<div class="mb-3">
<label for="due_date" class="form-label">Due Date</label>
<input type="text" class="form-control" id="datepicker_due_date" name="due_date" value="{{$data->due_date}}">
</div>
<div>
<label for="task_created_at" class="form-label">Task Created At</label>
<input type="datetime-local" class="form-control" id="task_created_at" name="task_created_at" value="{{$data->task_created_at}}" disabled>
</div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="d-flex justify-content-end">
<div class="mb-3"> <button type="button" id="btnUpdatePbgTask" class="btn btn-warning">
<dt>Name</dt> <span id="spinner" class="spinner-border spinner-border-sm me-1 d-none" role="status" aria-hidden="true"></span>
<dd>{{$data->name}}</dd> Update
</div> </button>
<div class="mb-3">
<dt>Owner Name</dt>
<dd>{{$data->owner_name}}</dd>
</div>
<div class="mb-3">
<dt>Aplication Type Name</dt>
<dd>{{$data->application_type_name}}</dd>
</div>
<div class="mb-3">
<dt>Condition</dt>
<dd>{{$data->condition}}</dd>
</div>
<div class="mb-3">
<dt>Registration Number</dt>
<dd>{{$data->registration_number}}</dd>
</div>
<div class="mb-3">
<dt>Document Number</dt>
<dd>{{$data->document_number}}</dd>
</div>
<div>
<dt>Status Name</dt>
<dd>{{$data->status_name}}</dd>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<dt>Address</dt>
<dd>{{$data->address}}</dd>
</div>
<div class="mb-3">
<dt>SLF Status Name</dt>
<dd>{{$data->slf_status_name}}</dd>
</div>
<div class="mb-3">
<dt>Function Type</dt>
<dd>{{$data->function_type}}</dd>
</div>
<div class="mb-3">
<dt>Consultation Type</dt>
<dd>{{$data->consultation_type}}</dd>
</div>
<div class="mb-3">
<dt>Due Date</dt>
<dd>{{$data->due_date}}</dd>
</div>
<div>
<dt>Task Created At</dt>
<dd>{{$data->task_created_at}}</dd>
</div>
</div> </div>
</div> </div>
</form>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -13,6 +13,19 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card w-100"> <div class="card w-100">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Laporan Rekap Data Pembayaran</h5>
<div class="d-flex gap-2">
<button class="btn btn-sm bg-black text-white d-flex align-items-center content-center gap-2" id="btn-export-excel" data-url="{{ route('api.district-payment-report.excel') }}">
<span>.xlsx</span>
<iconify-icon icon="mingcute:file-export-line" width="20" height="20" class="d-flex align-items-center"></iconify-icon>
</button>
<button class="btn btn-sm bg-black text-white d-flex align-items-center content-center gap-2" id="btn-export-pdf" data-url="{{ route('api.district-payment-report.pdf') }}">
<span>.pdf</span>
<iconify-icon icon="mingcute:file-export-line" width="20" height="20" class="d-flex align-items-center"></iconify-icon>
</button>
</div>
</div>
<div class="card-body"> <div class="card-body">
<div id="table-report-payment-recaps"></div> <div id="table-report-payment-recaps"></div>
</div> </div>

View File

@@ -13,6 +13,19 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card w-100"> <div class="card w-100">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Laporan PBG PTSP</h5>
<div class="d-flex gap-2">
<button class="btn btn-sm bg-black text-white d-flex align-items-center content-center gap-2" id="btn-export-excel" data-url="{{ route('api.report-ptsp.excel') }}">
<span>.xlsx</span>
<iconify-icon icon="mingcute:file-export-line" width="20" height="20" class="d-flex align-items-center"></iconify-icon>
</button>
<button class="btn btn-sm bg-black text-white d-flex align-items-center content-center gap-2" id="btn-export-pdf" data-url="{{ route('api.report-ptsp.pdf') }}">
<span>.pdf</span>
<iconify-icon icon="mingcute:file-export-line" width="20" height="20" class="d-flex align-items-center"></iconify-icon>
</button>
</div>
</div>
<div class="card-body"> <div class="card-body">
<div id="table-report-pbg-ptsp"></div> <div id="table-report-pbg-ptsp"></div>
</div> </div>

View File

@@ -9,8 +9,18 @@
<input id="tourism_based_KBLI" type="hidden" value="{{ json_encode($tourismBasedKBLI) }}"> <input id="tourism_based_KBLI" type="hidden" value="{{ json_encode($tourismBasedKBLI) }}">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title">Laporan Pariwisata</h5> <h5 class="card-title mb-0">Laporan Pariwisata</h5>
<div class="d-flex gap-2">
<button class="btn btn-sm bg-black text-white d-flex align-items-center content-center gap-2" id="btn-export-excel" data-url="{{ route('api.report-tourisms.excel') }}">
<span>.xlsx</span>
<iconify-icon icon="mingcute:file-export-line" width="20" height="20" class="d-flex align-items-center"></iconify-icon>
</button>
<button class="btn btn-sm bg-black text-white d-flex align-items-center content-center gap-2" id="btn-export-pdf" data-url="{{ route('api.report-tourisms.pdf') }}">
<span>.pdf</span>
<iconify-icon icon="mingcute:file-export-line" width="20" height="20" class="d-flex align-items-center"></iconify-icon>
</button>
</div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">

View File

@@ -12,6 +12,8 @@ use App\Http\Controllers\Api\LackOfPotentialController;
use App\Http\Controllers\Api\MenusController; use App\Http\Controllers\Api\MenusController;
use App\Http\Controllers\Api\PbgTaskController; use App\Http\Controllers\Api\PbgTaskController;
use App\Http\Controllers\Api\PbgTaskGoogleSheetsController; use App\Http\Controllers\Api\PbgTaskGoogleSheetsController;
use App\Http\Controllers\Api\ReportPbgPtspController;
use App\Http\Controllers\Api\ReportTourismsController;
use App\Http\Controllers\Api\RequestAssignmentController; use App\Http\Controllers\Api\RequestAssignmentController;
use App\Http\Controllers\Api\RolesController; use App\Http\Controllers\Api\RolesController;
use App\Http\Controllers\Api\ScrapingController; use App\Http\Controllers\Api\ScrapingController;
@@ -35,6 +37,7 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
Route::get('/users', 'index')->name('api.users'); Route::get('/users', 'index')->name('api.users');
Route::post('/users', 'store')->name('api.users.store'); Route::post('/users', 'store')->name('api.users.store');
Route::put('/users/{id}', 'update')->name('api.users.update'); Route::put('/users/{id}', 'update')->name('api.users.update');
Route::delete('/users/{id}','destroy')->name('api.users.destroy');
Route::post('/logout','logout')->name('api.users.logout'); Route::post('/logout','logout')->name('api.users.logout');
}); });
@@ -53,6 +56,10 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
Route::apiResource('request-assignments',RequestAssignmentController::class); Route::apiResource('request-assignments',RequestAssignmentController::class);
Route::get('/report-payment-recaps',[RequestAssignmentController::class, 'report_payment_recaps'])->name('report-payment-recaps'); Route::get('/report-payment-recaps',[RequestAssignmentController::class, 'report_payment_recaps'])->name('report-payment-recaps');
Route::get('/report-pbg-ptsp',[RequestAssignmentController::class, 'report_pbg_ptsp'])->name('report-pbg-ptsp'); Route::get('/report-pbg-ptsp',[RequestAssignmentController::class, 'report_pbg_ptsp'])->name('report-pbg-ptsp');
Route::controller(RequestAssignmentController::class)->group( function (){
Route::get('/district-payment-report/excel', 'export_excel_district_payment_recaps')->name('api.district-payment-report.excel');
Route::get('/district-payment-report/pdf', 'export_pdf_district_payment_recaps')->name('api.district-payment-report.pdf');
});
// all dashboards // all dashboards
Route::controller(DashboardController::class)->group(function(){ Route::controller(DashboardController::class)->group(function(){
@@ -65,7 +72,11 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
}); });
// scraping // scraping
Route::apiResource('/scraping', ScrapingController::class); Route::controller(ScrapingController::class)->group(function (){
Route::get('/scraping','index')->name('scraping');
Route::get('/retry-scraping/{id}','retry_syncjob')->name('retry-scraping');
});
// Route::apiResource('/scraping', ScrapingController::class);
// reklame // reklame
Route::apiResource('advertisements', AdvertisementController::class); Route::apiResource('advertisements', AdvertisementController::class);
@@ -98,9 +109,11 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
}); });
Route::apiResource('/api-pbg-task', PbgTaskController::class); Route::apiResource('/api-pbg-task', PbgTaskController::class);
Route::controller(PbgTaskController::class)->group( function (){
Route::put('/pbg-task/{task_uuid}/update', 'update')->name('api.pbg-task.update');
});
// sync pbg google sheet // sync pbg google sheet
Route::get('/sync-pbg-task-google-sheet', [PbgTaskController::class, 'syncPbgFromGoogleSheet'])->name('pbg-task.sync-google-sheet');
Route::apiResource('/api-google-sheet', GoogleSheetController::class); Route::apiResource('/api-google-sheet', GoogleSheetController::class);
Route::get('/sync-task', [SyncronizeController::class, 'syncPbgTask'])->name('api.task'); Route::get('/sync-task', [SyncronizeController::class, 'syncPbgTask'])->name('api.task');
Route::get('/get-user-token', [SyncronizeController::class, 'getUserToken'])->name('api.task.token'); Route::get('/get-user-token', [SyncronizeController::class, 'getUserToken'])->name('api.task.token');
@@ -144,6 +157,10 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
Route::get('/bigdata-resume', 'index')->name('api.bigdata-resume'); Route::get('/bigdata-resume', 'index')->name('api.bigdata-resume');
Route::get('/bigdata-report', 'bigdata_report')->name('api.bigdata-report'); Route::get('/bigdata-report', 'bigdata_report')->name('api.bigdata-report');
Route::get('/payment-recaps', 'payment_recaps')->name('api.payment-recaps'); Route::get('/payment-recaps', 'payment_recaps')->name('api.payment-recaps');
Route::get('/payment-recaps/excel', 'export_excel_payment_recaps')->name('api.payment-recaps.excel');
Route::get('/payment-recaps/pdf', 'export_pdf_payment_recaps')->name('api.payment-recaps.pdf');
Route::get('/report-director/excel', 'export_excel_report_director')->name('api.report-director.excel');
Route::get('/report-director/pdf', 'export_pdf_report_director')->name('api.report-director.pdf');
}); });
// task-assignments // task-assignments
@@ -153,4 +170,15 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
// pbg-task-google-sheet // pbg-task-google-sheet
Route::apiResource('pbg-task-google-sheet', PbgTaskGoogleSheetsController::class); Route::apiResource('pbg-task-google-sheet', PbgTaskGoogleSheetsController::class);
// export
Route::controller(ReportTourismsController::class)->group(function (){
Route::get('/report-tourisms/excel', 'export_excel')->name('api.report-tourisms.excel');
Route::get('/report-tourisms/pdf', 'export_pdf')->name('api.report-tourisms.pdf');
});
Route::controller(ReportPbgPtspController::class)->group( function (){
Route::get('/report-ptsp/excel', 'export_excel')->name('api.report-ptsp.excel');
Route::get('/report-ptsp/pdf', 'export_pdf')->name('api.report-ptsp.pdf');
});
}); });

View File

@@ -8,4 +8,4 @@ Artisan::command('inspire', function () {
$this->comment(Inspiring::quote()); $this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote')->hourly(); })->purpose('Display an inspiring quote')->hourly();
Schedule::command("app:execute-scraping")->dailyAt("00:00"); Schedule::command("app:scraping-data")->dailyAt("00:00");

View File

@@ -136,7 +136,9 @@ Route::group(['middleware' => 'auth'], function(){
// Report // Report
Route::group(['prefix' => '/report'], function(){ Route::group(['prefix' => '/report'], function(){
// Resource route, kecuali create karena dibuat terpisah // Resource route, kecuali create karena dibuat terpisah
Route::resource('/tourisms-report', ReportTourismController::class); Route::controller(ReportTourismController::class)->group(function (){
Route::get('/tourisms-report','index')->name('tourisms-report.index');
});
Route::controller(BigdataResumesController::class)->group(function (){ Route::controller(BigdataResumesController::class)->group(function (){
Route::get('/bigdata-resumes', 'index')->name('bigdata-resumes'); Route::get('/bigdata-resumes', 'index')->name('bigdata-resumes');

File diff suppressed because one or more lines are too long

View File

@@ -106,6 +106,7 @@ export default defineConfig({
//pbg-task //pbg-task
"resources/js/pbg-task/index.js", "resources/js/pbg-task/index.js",
"resources/js/pbg-task/show.js", "resources/js/pbg-task/show.js",
"resources/js/pbg-task/create.js",
// google-sheets // google-sheets
"resources/js/data/google-sheet/index.js", "resources/js/data/google-sheet/index.js",
// dummy // dummy