Compare commits

...

15 Commits

Author SHA1 Message Date
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
59 changed files with 2344 additions and 501 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

@@ -2,10 +2,12 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Jobs\ScrapingDataJob;
use App\Models\ImportDatasource; use App\Models\ImportDatasource;
use App\Services\ServiceGoogleSheet; use App\Services\ServiceGoogleSheet;
use App\Services\ServicePbgTask; use App\Services\ServicePbgTask;
use App\Services\ServiceTabPbgTask; use App\Services\ServiceTabPbgTask;
use App\Services\ServiceTokenSIMBG;
use GuzzleHttp\Client; // Import Guzzle Client use GuzzleHttp\Client; // Import Guzzle Client
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@@ -26,64 +28,68 @@ class ScrapingData extends Command
* @var string * @var string
*/ */
protected $description = 'Command description'; protected $description = 'Command description';
private $client;
private $service_pbg_task;
private $service_tab_pbg_task;
/** /**
* Inject dependencies. * Inject dependencies.
*/ */
public function __construct(Client $client, ServicePbgTask $service_pbg_task, ServiceTabPbgTask $serviceTabPbgTask) public function __construct(
{ ) {
parent::__construct(); parent::__construct();
$this->client = $client; }
$this->service_pbg_task = $service_pbg_task;
$this->service_tab_pbg_task = $serviceTabPbgTask; public function handle()
{
dispatch(new ScrapingDataJob());
$this->info("Scraping job dispatched successfully");
} }
/** /**
* Execute the console command. * Execute the console command.
*/ */
public function handle() // public function handle()
{ // {
try { // try {
// Create a record with "processing" status // // Create a record with "processing" status
$import_datasource = ImportDatasource::create([ // $import_datasource = ImportDatasource::create([
'message' => 'Initiating scraping...', // 'message' => 'Initiating scraping...',
'response_body' => null, // 'response_body' => null,
'status' => 'processing' // 'status' => 'processing',
]); // 'start_time' => now()
// ]);
// Run the service // // Run the service
$service_google_sheet = new ServiceGoogleSheet($import_datasource->id); // $service_google_sheet = new ServiceGoogleSheet();
$service_google_sheet->run_service(); // $service_google_sheet->run_service();
// Run the ServicePbgTask with injected Guzzle Client // // Run the ServicePbgTask with injected Guzzle Client
$this->service_pbg_task->run_service(); // $this->service_pbg_task->run_service();
// run the service pbg task assignments // // run the service pbg task assignments
$this->service_tab_pbg_task->run_service(); // $this->service_tab_pbg_task->run_service();
// Update the record status to "success" after completion // // Update the record status to "success" after completion
$import_datasource->update([ // $import_datasource->update([
'status' => 'success', // 'status' => 'success',
'message' => 'Scraping completed successfully.' // 'message' => 'Scraping completed successfully.',
]); // 'finish_time' => now()
// ]);
} catch (\Exception $e) { // } 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()
// ]);
// }
// }
// }
// 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()
]);
}
}
}
} }

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

@@ -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,15 @@ 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\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,8 +29,11 @@ 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"]);
} }

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,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,11 +15,18 @@ 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(),
]; ];

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,77 @@
<?php
namespace App\Jobs;
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()
]);
// Run the scraping services
$service_google_sheet->run_service();
$service_pbg_task->run_service();
$service_tab_pbg_task->run_service();
// 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()
]);
}
// 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

@@ -5,6 +5,7 @@ use App\Models\DataSetting;
use App\Models\ImportDatasource; use App\Models\ImportDatasource;
use App\Models\PbgTaskGoogleSheet; use App\Models\PbgTaskGoogleSheet;
use Carbon\Carbon; use Carbon\Carbon;
use Exception;
use Google_Client; use Google_Client;
use Google_Service_Sheets; use Google_Service_Sheets;
use Log; use Log;
@@ -15,7 +16,7 @@ class ServiceGoogleSheet
protected $spreadsheetID; protected $spreadsheetID;
protected $service_sheets; protected $service_sheets;
protected $import_datasource; protected $import_datasource;
public function __construct(int $import_datasource_id) public function __construct()
{ {
$this->client = new Google_Client(); $this->client = new Google_Client();
$this->client->setApplicationName("Sibedas Google Sheets API"); $this->client->setApplicationName("Sibedas Google Sheets API");
@@ -27,21 +28,15 @@ class ServiceGoogleSheet
$this->spreadsheetID = env("SPREAD_SHEET_ID"); $this->spreadsheetID = env("SPREAD_SHEET_ID");
$this->service_sheets = new Google_Service_Sheets($this->client); $this->service_sheets = new Google_Service_Sheets($this->client);
$this->import_datasource = ImportDatasource::findOrFail($import_datasource_id);
} }
public function run_service(){ public function run_service(){
$run_one = $this->sync_big_data(); try{
if(!$run_one){ $this->sync_big_data();
$this->import_datasource->update(['status' => 'failed']); $this->sync_google_sheet_data();
return false; }catch(Exception $e){
throw $e;
} }
$run_two = $this->sync_google_sheet_data();
if(!$run_two){
$this->import_datasource->update(['status' => 'failed']);
return false;
}
return true;
} }
public function sync_google_sheet_data() { public function sync_google_sheet_data() {
try { try {
@@ -49,7 +44,7 @@ class ServiceGoogleSheet
if (empty($sheet_data) || count($sheet_data) < 2) { if (empty($sheet_data) || count($sheet_data) < 2) {
Log::warning("sync_google_sheet_data: No valid data found."); Log::warning("sync_google_sheet_data: No valid data found.");
return false; throw new \Exception("sync_google_sheet_data: No valid data found.");
} }
$cleanValue = function ($value) { $cleanValue = function ($value) {
@@ -157,9 +152,10 @@ class ServiceGoogleSheet
} }
Log::info("sync google sheet done"); Log::info("sync google sheet done");
return true;
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error("sync_google_sheet_data failed", ['error' => $e->getMessage()]); Log::error("sync_google_sheet_data failed", ['error' => $e->getMessage()]);
return false; throw $e;
} }
} }

View File

@@ -115,10 +115,15 @@ class ServiceTabPbgTask
} catch (\GuzzleHttp\Exception\ClientException $e) { } catch (\GuzzleHttp\Exception\ClientException $e) {
if ($e->getCode() === 401 && !$retriedAfter401) { if ($e->getCode() === 401 && !$retriedAfter401) {
Log::warning("401 Unauthorized - Refreshing token and retrying..."); Log::warning("401 Unauthorized - Refreshing token and retrying...");
$this->refreshToken(); try{
$options['headers']['Authorization'] = "Bearer {$this->user_token}"; $this->refreshToken();
$retriedAfter401 = true; $options['headers']['Authorization'] = "Bearer {$this->user_token}";
continue; // Retry with new token $retriedAfter401 = true;
continue;
}catch(\Exception $refreshError){
Log::error("Token refresh and login failed: " . $refreshError->getMessage());
return false;
}
} }
throw $e; throw $e;
@@ -221,10 +226,15 @@ class ServiceTabPbgTask
} catch (\GuzzleHttp\Exception\ClientException $e) { } catch (\GuzzleHttp\Exception\ClientException $e) {
if ($e->getCode() === 401 && !$retriedAfter401) { if ($e->getCode() === 401 && !$retriedAfter401) {
Log::warning("401 Unauthorized - Refreshing token and retrying..."); Log::warning("401 Unauthorized - Refreshing token and retrying...");
$this->refreshToken(); try{
$options['headers']['Authorization'] = "Bearer {$this->user_token}"; $this->refreshToken();
$retriedAfter401 = true; $options['headers']['Authorization'] = "Bearer {$this->user_token}";
continue; $retriedAfter401 = true;
continue;
}catch(\Exception $refreshError){
Log::error("Token refresh and login failed: " . $refreshError->getMessage());
return false;
}
} }
return false; return false;
@@ -295,10 +305,15 @@ class ServiceTabPbgTask
} catch (\GuzzleHttp\Exception\ClientException $e) { } catch (\GuzzleHttp\Exception\ClientException $e) {
if ($e->getCode() === 401 && !$retriedAfter401) { if ($e->getCode() === 401 && !$retriedAfter401) {
Log::warning("401 Unauthorized - Refreshing token and retrying..."); Log::warning("401 Unauthorized - Refreshing token and retrying...");
$this->refreshToken(); try{
$options['headers']['Authorization'] = "Bearer {$this->user_token}"; $this->refreshToken();
$retriedAfter401 = true; $options['headers']['Authorization'] = "Bearer {$this->user_token}";
continue; $retriedAfter401 = true;
continue;
}catch(\Exception $refreshError){
Log::error("Token refresh and login failed: " . $refreshError->getMessage());
return false;
}
} }
return false; return false;
@@ -329,22 +344,64 @@ class ServiceTabPbgTask
private function refreshToken() private function refreshToken()
{ {
try { $maxRetries = 3; // Maximum retry attempts
$attempt = 0;
$newAuthToken = $this->service_token->refresh_token($this->user_refresh_token); while ($attempt < $maxRetries) {
try {
$attempt++;
Log::info("Attempt $attempt: Refreshing token...");
$this->user_token = $newAuthToken['access']; $newAuthToken = $this->service_token->refresh_token($this->user_refresh_token);
$this->user_refresh_token = $newAuthToken['refresh'];
if (!$this->user_token) { if (!isset($newAuthToken['access']) || !isset($newAuthToken['refresh'])) {
Log::error("Token refresh failed: No token received."); throw new \Exception("Invalid refresh token response.");
throw new \Exception("Failed to refresh token."); }
$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
} }
}
Log::info("Token refreshed successfully."); // If refresh fails after retries, attempt re-login
} catch (\Exception $e) { $attempt = 0;
Log::error("Token refresh error: " . $e->getMessage()); while ($attempt < $maxRetries) {
throw new \Exception("Token refresh failed."); 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

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

@@ -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() {
@@ -114,6 +116,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,188 +245,9 @@ 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 {
const response = await fetch( const response = await fetch(

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
item.id,
item.name, return data.data.map((item, index) => {
item.url, console.log(`Item ${index + 1}:`, item); // Log each item
item.icon, return [
item.parent_id, item.id,
item.sort_order, item.name,
item.id, item.url,
]), item.icon,
item.parent?.name,
item.sort_order,
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,64 +53,208 @@ class PaymentRecaps {
initTablePaymentRecaps() { initTablePaymentRecaps() {
let tableContainer = document.getElementById("table-payment-recaps"); let tableContainer = document.getElementById("table-payment-recaps");
this.table = new Grid({ // Fetch data from the server
columns: [ fetch(
{ name: "Kategori", data: (row) => row[0] }, `${GlobalConfig.apiHost}/api/payment-recaps?start_date=${
{ name: "Nominal", data: (row) => row[1] }, this.startDate || ""
{ }&end_date=${this.endDate || ""}`,
name: "Created", {
data: (row) => row[2],
attributes: { style: "width: 200px; white-space: nowrap;" },
},
],
pagination: {
limit: 10,
server: {
url: (prev, page) =>
`${prev}${prev.includes("?") ? "&" : "?"}page=${
page + 1
}`,
},
},
sort: true,
server: {
url: `${GlobalConfig.apiHost}/api/payment-recaps`,
headers: { headers: {
Authorization: `Bearer ${document Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]') .querySelector('meta[name="api-token"]')
.getAttribute("content")}`, .getAttribute("content")}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
then: (response) => { }
console.log("API Response:", response); // Debugging )
.then((response) => response.json())
.then((data) => {
if (!data || !Array.isArray(data.data)) {
console.error("Error: Data is not an array", data);
return;
}
if (!response.data || !Array.isArray(response.data)) { let formattedData = data.data.map((item) => [
console.error( this.formatCategory(item.category ?? "Unknown"),
"Error: Data is not an array", addThousandSeparators(Number(item.nominal).toString() || 0),
response.data moment(item.created_at).isValid()
); ? moment(item.created_at).format("YYYY-MM-DD H:mm:ss")
return []; : "-",
} ]);
return response.data.map((item) => [ // 🔥 If the table already exists, update it instead of re-creating
this.formatCategory(item.category ?? "Unknown"), // Ensure category is not null if (this.table) {
addThousandSeparators( this.table
Number(item.nominal).toString() || 0 .updateConfig({
), // Ensure nominal is a valid number data: formattedData.length > 0 ? formattedData : [],
moment(item.created_at).isValid() })
? moment(item.created_at).format( .forceRender();
"YYYY-MM-DD H:mm:ss" } else {
) // 🔹 First-time initialization
: "-", // Handle invalid dates this.table = new Grid({
]); columns: [
}, { name: "Kategori", data: (row) => row[0] },
total: (response) => response.pagination?.total || 0, { name: "Nominal", data: (row) => row[1] },
}, {
width: "auto", name: "Created",
fixedHeader: true, data: (row) => row[2],
}).render(tableContainer); attributes: {
style: "width: 200px; white-space: nowrap;",
},
},
],
pagination: {
limit: 50,
},
sort: true,
data: formattedData.length > 0 ? formattedData : [],
width: "auto",
fixedHeader: true,
}).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

@@ -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) => [
search: true, // Menambahkan fitur pencarian item.kbli_title,
pagination: true, // Menambahkan fitur pagination item.total_records,
sort: true, // Menambahkan fitur sorting ]), // Mengubah data untuk Grid.js
search: true, // Menambahkan fitur pencarian
pagination: true, // Menambahkan fitur pagination
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,16 @@ 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",
],
search: { search: {
server: { server: {
url: (prev, keyword) => `${prev}?search=${keyword}`, url: (prev, keyword) => `${prev}?search=${keyword}`,
@@ -49,6 +58,9 @@ 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,
]), ]),
total: (data) => data.meta.total, total: (data) => data.meta.total,
@@ -148,8 +160,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

@@ -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(){
@@ -144,6 +151,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 +164,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');

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