Compare commits
6 Commits
e577da737b
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91085e8796 | ||
|
|
61e6eb9803 | ||
|
|
148dfebb4a | ||
|
|
aa34fff979 | ||
|
|
1a24b18719 | ||
|
|
e265e2ec35 |
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\ServiceGoogleSheet;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ScrapingLeaderData extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:scraping-leader-data';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Scraping leader data from google spreadsheet and save to database';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$service_google_sheet = app(ServiceGoogleSheet::class);
|
||||
$service_google_sheet->sync_leader_data();
|
||||
$this->info('Leader data synced successfully');
|
||||
}
|
||||
}
|
||||
@@ -76,19 +76,19 @@ class BigDataResumeController extends Controller
|
||||
$total_potensi_percentage = $big_data_resume->potention_sum > 0 && $target_pad > 0
|
||||
? round(($big_data_resume->potention_sum / $target_pad) * 100, 2) : 0;
|
||||
|
||||
// percentage verified document (verified_sum / potention_sum) - by value/amount
|
||||
$verified_percentage = $big_data_resume->potention_sum > 0 && $big_data_resume->verified_sum >= 0
|
||||
? round(($big_data_resume->verified_sum / $big_data_resume->potention_sum) * 100, 2) : 0;
|
||||
// // percentage verified document (verified_sum / potention_sum) - by value/amount
|
||||
// $verified_percentage = $big_data_resume->potention_sum > 0 && $big_data_resume->verified_sum >= 0
|
||||
// ? round(($big_data_resume->verified_sum / $big_data_resume->potention_sum) * 100, 2) : 0;
|
||||
|
||||
// percentage non-verified document (non_verified_sum / potention_sum) - by value/amount
|
||||
$non_verified_percentage = $big_data_resume->potention_sum > 0 && $big_data_resume->non_verified_sum >= 0
|
||||
? round(($big_data_resume->non_verified_sum / $big_data_resume->potention_sum) * 100, 2) : 0;
|
||||
// // percentage non-verified document (non_verified_sum / potention_sum) - by value/amount
|
||||
// $non_verified_percentage = $big_data_resume->potention_sum > 0 && $big_data_resume->non_verified_sum >= 0
|
||||
// ? round(($big_data_resume->non_verified_sum / $big_data_resume->potention_sum) * 100, 2) : 0;
|
||||
|
||||
// Alternative: percentage by count (if needed)
|
||||
// $verified_count_percentage = $big_data_resume->potention_count > 0
|
||||
// ? round(($big_data_resume->verified_count / $big_data_resume->potention_count) * 100, 2) : 0;
|
||||
// $non_verified_count_percentage = $big_data_resume->potention_count > 0
|
||||
// ? round(($big_data_resume->non_verified_count / $big_data_resume->potention_count) * 100, 2) : 0;
|
||||
$verified_count_percentage = $big_data_resume->potention_count > 0 && $big_data_resume->verified_count > 0
|
||||
? round(($big_data_resume->verified_count / $big_data_resume->potention_count) * 100, 2) : 0;
|
||||
$non_verified_count_percentage = $big_data_resume->potention_count > 0 && $big_data_resume->non_verified_count > 0
|
||||
? round(($big_data_resume->non_verified_count / $big_data_resume->potention_count) * 100, 2) : 0;
|
||||
|
||||
// percentage business document (business / non_verified)
|
||||
$business_percentage = $big_data_resume->non_verified_sum > 0 && $big_data_resume->business_sum >= 0
|
||||
@@ -146,12 +146,12 @@ class BigDataResumeController extends Controller
|
||||
'verified_document' => [
|
||||
'sum' => (float) $big_data_resume->verified_sum,
|
||||
'count' => $big_data_resume->verified_count,
|
||||
'percentage' => $verified_percentage
|
||||
'percentage' => $verified_count_percentage
|
||||
],
|
||||
'non_verified_document' => [
|
||||
'sum' => (float) $big_data_resume->non_verified_sum,
|
||||
'count' => $big_data_resume->non_verified_count,
|
||||
'percentage' => $non_verified_percentage
|
||||
'percentage' => $non_verified_count_percentage
|
||||
],
|
||||
'business_document' => [
|
||||
'sum' => (float) $big_data_resume->business_sum,
|
||||
|
||||
@@ -12,7 +12,6 @@ use App\Models\DataSetting;
|
||||
use App\Models\ImportDatasource;
|
||||
use App\Models\PbgTask;
|
||||
use App\Models\PbgTaskGoogleSheet;
|
||||
use App\Services\GoogleSheetService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -20,10 +19,6 @@ use Illuminate\Validation\Rules\Enum;
|
||||
|
||||
class PbgTaskController extends Controller
|
||||
{
|
||||
protected $googleSheetService;
|
||||
public function __construct(GoogleSheetService $googleSheetService){
|
||||
$this->googleSheetService = $googleSheetService;
|
||||
}
|
||||
public function index(Request $request)
|
||||
{
|
||||
info($request);
|
||||
|
||||
@@ -335,7 +335,7 @@ class RequestAssignmentController extends Controller
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereHas('pbg_task_details', function ($q4) {
|
||||
->whereHas('pbg_task_detail', function ($q4) {
|
||||
$q4->where('unit', '>', 1);
|
||||
});
|
||||
});
|
||||
@@ -530,7 +530,7 @@ class RequestAssignmentController extends Controller
|
||||
})
|
||||
->orWhereNull('function_type');
|
||||
})
|
||||
->whereHas('pbg_task_details', function ($q4) {
|
||||
->whereHas('pbg_task_detail', function ($q4) {
|
||||
$q4->where('unit', '>', 1);
|
||||
});
|
||||
});
|
||||
@@ -554,10 +554,10 @@ class RequestAssignmentController extends Controller
|
||||
->whereIn("status", PbgTaskStatus::getNonVerified())
|
||||
// Additional condition: unit IS NULL OR unit <= 1
|
||||
->where(function ($q3) {
|
||||
$q3->whereDoesntHave('pbg_task_details', function ($q4) {
|
||||
$q3->whereDoesntHave('pbg_task_detail', function ($q4) {
|
||||
$q4->where('unit', '>', 1);
|
||||
})
|
||||
->orWhereDoesntHave('pbg_task_details');
|
||||
->orWhereDoesntHave('pbg_task_detail');
|
||||
});
|
||||
})
|
||||
->where('is_valid', true)
|
||||
|
||||
@@ -3,17 +3,12 @@
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\ServiceSIMBG;
|
||||
use Illuminate\Http\Request;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
class SyncronizeController extends Controller
|
||||
{
|
||||
protected $service_simbg;
|
||||
public function __construct(ServiceSIMBG $service_simbg){
|
||||
$this->service_simbg = $service_simbg;
|
||||
}
|
||||
public function index(Request $request){
|
||||
$menuId = $request->query('menu_id');
|
||||
$user = Auth::user();
|
||||
@@ -37,36 +32,4 @@ class SyncronizeController extends Controller
|
||||
|
||||
return view('settings.syncronize.index', compact('creator', 'updater', 'destroyer'));
|
||||
}
|
||||
|
||||
public function syncPbgTask(){
|
||||
$res = $this->service_simbg->syncTaskPBG();
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function syncronizeTask(Request $request){
|
||||
$res = $this->service_simbg->syncTaskPBG();
|
||||
return redirect()->back()->with('success', 'Processing completed successfully');
|
||||
}
|
||||
|
||||
public function getUserToken(){
|
||||
$res = $this->service_simbg->getToken();
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function syncIndexIntegration(Request $request, $uuid){
|
||||
$token = $request->get('token');
|
||||
$res = $this->service_simbg->syncIndexIntegration($uuid);
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function syncTaskDetailSubmit(Request $request, $uuid){
|
||||
$token = $request->get('token');
|
||||
$res = $this->service_simbg->syncTaskDetailSubmit($uuid, $token);
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function syncTaskAssignments($uuid){
|
||||
$res = $this->service_simbg->syncTaskAssignments($uuid);
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,11 @@ class MenuResource extends JsonResource
|
||||
'url' => $this->url,
|
||||
'sort_order' => $this->sort_order,
|
||||
'parent' => $this->parent ? new MenuResource($this->parent) : null,
|
||||
'children' => $this->when($this->relationLoaded('children'), function () {
|
||||
return $this->children->sortBy('sort_order')->map(function ($child) {
|
||||
return new MenuResource($child);
|
||||
});
|
||||
}),
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at
|
||||
];
|
||||
|
||||
@@ -59,28 +59,12 @@ class ScrapingDataJob implements ShouldQueue
|
||||
'start_time' => now(),
|
||||
'failed_uuid' => null
|
||||
]);
|
||||
|
||||
Log::info("ImportDatasource created", ['id' => $import_datasource->id]);
|
||||
|
||||
// STEP 1: Scrape Google Sheet data first
|
||||
Log::info("=== STEP 1: SCRAPING GOOGLE SHEET ===");
|
||||
$import_datasource->update(['message' => 'Scraping Google Sheet data...']);
|
||||
|
||||
$service_google_sheet->run_service();
|
||||
Log::info("Google Sheet scraping completed successfully");
|
||||
|
||||
// STEP 2: Scrape PBG Task to get parent data
|
||||
Log::info("=== STEP 2: SCRAPING PBG TASK PARENT DATA ===");
|
||||
$import_datasource->update(['message' => 'Scraping PBG Task parent data...']);
|
||||
|
||||
$service_pbg_task->run_service();
|
||||
Log::info("PBG Task parent data scraping completed");
|
||||
|
||||
// STEP 3: Get all PBG tasks for detail scraping
|
||||
$totalTasks = PbgTask::count();
|
||||
Log::info("=== STEP 3: SCRAPING PBG TASK DETAILS ===", [
|
||||
'total_tasks' => $totalTasks
|
||||
]);
|
||||
|
||||
$import_datasource->update([
|
||||
'message' => "Scraping details for {$totalTasks} PBG tasks..."
|
||||
@@ -135,13 +119,10 @@ class ScrapingDataJob implements ShouldQueue
|
||||
}
|
||||
});
|
||||
|
||||
Log::info("Task details scraping completed", [
|
||||
'processed_tasks' => $processedTasks,
|
||||
'total_tasks' => $totalTasks
|
||||
]);
|
||||
$import_datasource->update(['message' => 'Scraping Google Sheet data...']);
|
||||
|
||||
$service_google_sheet->run_service();
|
||||
|
||||
// STEP 4: Generate BigData Resume
|
||||
Log::info("=== STEP 4: GENERATING BIGDATA RESUME ===");
|
||||
$import_datasource->update(['message' => 'Generating BigData resume...']);
|
||||
|
||||
BigdataResume::generateResumeData($import_datasource->id, date('Y'), "simbg");
|
||||
|
||||
@@ -14,8 +14,6 @@ use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Carbon\Carbon;
|
||||
use App\Services\ServiceSIMBG;
|
||||
use App\Services\GoogleSheetService;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
@@ -64,7 +62,8 @@ class AppServiceProvider extends ServiceProvider
|
||||
$query->whereHas('roles', function ($subQuery) use ($user) {
|
||||
$subQuery->whereIn('roles.id', $user->roles->pluck('id'))
|
||||
->where('role_menu.allow_show', 1);
|
||||
});
|
||||
})
|
||||
->orderBy('sort_order', 'asc');
|
||||
}])
|
||||
->whereNull('parent_id') // Ambil hanya menu utama
|
||||
->orderBy('sort_order', 'asc')
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Google_Client;
|
||||
use Google_Service_Sheets;
|
||||
|
||||
class GoogleSheetService
|
||||
{
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
protected $client;
|
||||
protected $service;
|
||||
protected $spreadsheetID;
|
||||
protected $service_sheets;
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new Google_Client();
|
||||
$this->client->setApplicationName("Sibedas Google Sheets API");
|
||||
$this->client->setScopes([Google_Service_Sheets::SPREADSHEETS_READONLY]);
|
||||
$this->client->setAuthConfig(storage_path("app/teak-banner-450003-s8-ea05661d9db0.json"));
|
||||
$this->client->setAccessType("offline");
|
||||
|
||||
$this->service = new Google_Service_Sheets($this->client);
|
||||
$this->spreadsheetID = env("SPREAD_SHEET_ID");
|
||||
|
||||
$this->service_sheets = new Google_Service_Sheets($this->client);
|
||||
}
|
||||
|
||||
public function getSheetData($range){
|
||||
$response = $this->service->spreadsheets_values->get($this->spreadsheetID, $range);
|
||||
return $response->getValues();
|
||||
}
|
||||
|
||||
public function getLastRowByColumn($column = "A")
|
||||
{
|
||||
try{
|
||||
// Ambil spreadsheet
|
||||
$spreadsheet = $this->service->spreadsheets->get($this->spreadsheetID);
|
||||
$sheets = $spreadsheet->getSheets();
|
||||
|
||||
if (!empty($sheets)) {
|
||||
// Ambil nama sheet pertama dengan benar
|
||||
$firstSheetTitle = $sheets[0]->getProperties()->getTitle();
|
||||
|
||||
// ✅ Format range harus benar!
|
||||
$range = "{$firstSheetTitle}!{$column}:{$column}";
|
||||
|
||||
// Ambil data dari kolom yang diminta
|
||||
$response = $this->service->spreadsheets_values->get($this->spreadsheetID, $range);
|
||||
$values = $response->getValues();
|
||||
|
||||
// Cek nilai terakhir yang tidak kosong
|
||||
$lastRow = 0;
|
||||
if (!empty($values)) {
|
||||
foreach ($values as $index => $row) {
|
||||
if (!empty($row[0])) { // Jika ada data, update lastRow
|
||||
$lastRow = $index + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $lastRow;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}catch(\Exception $e){
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
public function getHeader()
|
||||
{
|
||||
try{
|
||||
$spreadsheet = $this->service->spreadsheets->get($this->spreadsheetID);
|
||||
$sheets = $spreadsheet->getSheets();
|
||||
|
||||
// Ambil nama sheet pertama
|
||||
$firstSheetTitle = $sheets[0]->getProperties()->getTitle();
|
||||
|
||||
// Ambil data dari baris pertama (header)
|
||||
$range = "{$firstSheetTitle}!1:1";
|
||||
$response = $this->service->spreadsheets_values->get($this->spreadsheetID, $range);
|
||||
$values = $response->getValues();
|
||||
|
||||
// Kembalikan header (baris pertama)
|
||||
return !empty($values) ? $values[0] : [];
|
||||
}catch(\Exception $e){
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function getLastColumn()
|
||||
{
|
||||
$spreadsheet = $this->service->spreadsheets->get($this->spreadsheetID);
|
||||
$sheets = $spreadsheet->getSheets();
|
||||
|
||||
// Ambil nama sheet pertama
|
||||
$firstSheetTitle = $sheets[0]->getProperties()->getTitle();
|
||||
|
||||
// Ambil baris pertama untuk mendapatkan jumlah kolom yang terisi
|
||||
$range = "{$firstSheetTitle}!1:1";
|
||||
$response = $this->service->spreadsheets_values->get($this->spreadsheetID, $range);
|
||||
$values = $response->getValues();
|
||||
|
||||
// Hitung jumlah kolom yang memiliki nilai
|
||||
return !empty($values) ? count(array_filter($values[0], fn($value) => $value !== "")) : 0;
|
||||
}
|
||||
|
||||
public function getSheetDataCollection($totalRow = 10){
|
||||
try{
|
||||
$spreadsheet = $this->service->spreadsheets->get($this->spreadsheetID);
|
||||
$sheets = $spreadsheet->getSheets();
|
||||
$firstSheetTitle = $sheets[0]->getProperties()->getTitle();
|
||||
|
||||
$header = $this->getHeader();
|
||||
$header = array_map(function($columnHeader) {
|
||||
// Trim spaces first, then replace non-alphanumeric characters with underscores
|
||||
$columnHeader = trim($columnHeader);
|
||||
return strtolower(preg_replace('/[^A-Za-z0-9_]/', '_', $columnHeader));
|
||||
}, $header);
|
||||
$range = "{$firstSheetTitle}!2:{$totalRow}";
|
||||
|
||||
$response = $this->service->spreadsheets_values->get($this->spreadsheetID, $range);
|
||||
$values = $response->getValues();
|
||||
|
||||
$mappedData = [];
|
||||
if (!empty($values)) {
|
||||
foreach ($values as $row) {
|
||||
$rowData = [];
|
||||
foreach ($header as $index => $columnHeader) {
|
||||
// Map header to the corresponding value from the row
|
||||
$rowData[$columnHeader] = isset($row[$index]) ? $row[$index] : null;
|
||||
}
|
||||
$mappedData[] = $rowData;
|
||||
}
|
||||
}
|
||||
|
||||
return $mappedData;
|
||||
}catch(\Exception $e){
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
public function get_data_by_sheet($no_sheet = 1){
|
||||
$spreadsheet = $this->service->spreadsheets->get($this->spreadsheetID);
|
||||
$sheets = $spreadsheet->getSheets();
|
||||
$sheetTitle = $sheets[$no_sheet]->getProperties()->getTitle();
|
||||
$range = "{$sheetTitle}";
|
||||
$response = $this->service->spreadsheets_values->get($this->spreadsheetID, $range);
|
||||
$values = $response->getValues();
|
||||
return!empty($values)? $values : [];
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
use App\Traits\GlobalApiResponse;
|
||||
use GuzzleHttp\Client;
|
||||
use Exception;
|
||||
class ServiceClient
|
||||
{
|
||||
use GlobalApiResponse;
|
||||
private $client;
|
||||
private $baseUrl;
|
||||
private $headers;
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
public function __construct($baseUrl = '', $headers = [])
|
||||
{
|
||||
$this->client = new Client();
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->headers = array_merge(
|
||||
[
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json'
|
||||
],
|
||||
$headers
|
||||
);
|
||||
}
|
||||
|
||||
public function makeRequest($url, $method = 'GET', $body = null, $headers = [], $timeout = 14400){
|
||||
try {
|
||||
|
||||
$headers = array_merge($this->headers, $headers);
|
||||
|
||||
$options = [
|
||||
'headers' => $headers,
|
||||
'timeout' => $timeout,
|
||||
'connect_timeout' => 60
|
||||
];
|
||||
|
||||
if ($body) {
|
||||
$options['json'] = $body; // Guzzle akan mengonversi array ke JSON
|
||||
}
|
||||
|
||||
$response = $this->client->request($method, $this->baseUrl . $url, $options);
|
||||
$responseBody = (string) $response->getBody();
|
||||
|
||||
if (!str_contains($response->getHeaderLine('Content-Type'), 'application/json')) {
|
||||
\Log::error('Unexpected response format: ' . $responseBody);
|
||||
return $this->resError('API response is not JSON');
|
||||
}
|
||||
|
||||
$resultResponse = json_decode($responseBody, true, 512, JSON_THROW_ON_ERROR);
|
||||
return $this->resSuccess($resultResponse);
|
||||
} catch (\GuzzleHttp\Exception\ClientException $e) {
|
||||
// Handle 4xx errors (e.g., 401 Unauthorized)
|
||||
$responseBody = (string) $e->getResponse()->getBody();
|
||||
$errorResponse = json_decode($responseBody, true);
|
||||
|
||||
if (isset($errorResponse['code']) && $errorResponse['code'] === 'token_not_valid') {
|
||||
return $this->resError('Invalid token, please refresh your token.', $errorResponse, 401);
|
||||
}
|
||||
|
||||
return $this->resError('Client error from API', $errorResponse, $e->getResponse()->getStatusCode());
|
||||
} catch (\GuzzleHttp\Exception\ServerException $e) {
|
||||
// Handle 5xx errors (e.g., Internal Server Error)
|
||||
return $this->resError('Server error from API', (string) $e->getResponse()->getBody(), 500);
|
||||
} catch (\GuzzleHttp\Exception\RequestException $e) {
|
||||
// Handle network errors (e.g., timeout, connection issues)
|
||||
return $this->resError('Network error: ' . $e->getMessage(), null, 503);
|
||||
} catch (Exception $e) {
|
||||
// Handle unexpected errors
|
||||
return $this->resError('Unexpected error: ' . $e->getMessage(), null, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Fungsi untuk melakukan permintaan GET
|
||||
public function get($url, $headers = [])
|
||||
{
|
||||
return $this->makeRequest($url, 'GET', null, $headers);
|
||||
}
|
||||
|
||||
// Fungsi untuk melakukan permintaan POST
|
||||
public function post($url, $body, $headers = [])
|
||||
{
|
||||
return $this->makeRequest($url, 'POST', $body, $headers);
|
||||
}
|
||||
|
||||
// Fungsi untuk melakukan permintaan PUT
|
||||
public function put($url, $body, $headers = [])
|
||||
{
|
||||
return $this->makeRequest($url, 'PUT', $body, $headers);
|
||||
}
|
||||
|
||||
// Fungsi untuk melakukan permintaan DELETE
|
||||
public function delete($url, $headers = [])
|
||||
{
|
||||
return $this->makeRequest($url, 'DELETE', null, $headers);
|
||||
}
|
||||
}
|
||||
@@ -688,21 +688,27 @@ class ServiceGoogleSheet
|
||||
|
||||
// Build and insert in small batches to avoid high memory usage
|
||||
$batch = [];
|
||||
$batchSize = 500;
|
||||
$inserted = 0;
|
||||
// Stream rows in chunks from API to avoid loading full sheet
|
||||
$rowStart = 2; // data starts from row 2
|
||||
$chunkRowSize = 800; // number of rows per chunk
|
||||
$chunkRowSize = 1000; // number of rows per chunk
|
||||
$inserted = 0;
|
||||
while (true) {
|
||||
$rowEnd = $rowStart + $chunkRowSize - 1;
|
||||
$range = sprintf('%s!%s%d:%s%d', $sheetName, $startLetter, $rowStart, 'BF', $rowEnd);
|
||||
$resp = $this->service->spreadsheets_values->get($this->spreadsheetID, $range);
|
||||
$values = $resp->getValues() ?? [];
|
||||
|
||||
|
||||
if (empty($values)) {
|
||||
break; // no more rows
|
||||
}
|
||||
|
||||
|
||||
Log::info('Chunk fetched', [
|
||||
'rowStart' => $rowStart,
|
||||
'rowEnd' => $rowEnd,
|
||||
'count' => count($values)
|
||||
]);
|
||||
// Preload registration map for this chunk
|
||||
$chunkRegs = [];
|
||||
foreach ($values as $row) {
|
||||
@@ -838,11 +844,6 @@ class ServiceGoogleSheet
|
||||
if (function_exists('gc_collect_cycles')) { gc_collect_cycles(); }
|
||||
}
|
||||
|
||||
if (!empty($batch)) {
|
||||
\App\Models\PbgTaskPayment::insert($batch);
|
||||
$inserted += count($batch);
|
||||
}
|
||||
|
||||
Log::info('PBG Task Payments reloaded from sheet', ['inserted' => $inserted]);
|
||||
|
||||
return ['success' => true, 'inserted' => $inserted];
|
||||
|
||||
@@ -1,662 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\ImportDatasourceStatus;
|
||||
use App\Models\BigdataResume;
|
||||
use App\Models\GlobalSetting;
|
||||
use App\Models\ImportDatasource;
|
||||
use App\Models\PbgTaskIndexIntegrations;
|
||||
use App\Models\PbgTaskPrasarana;
|
||||
use App\Models\PbgTaskRetributions;
|
||||
use App\Models\TaskAssignment;
|
||||
use Exception;
|
||||
use App\Models\PbgTask;
|
||||
use App\Traits\GlobalApiResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Carbon\Carbon;
|
||||
use App\Services\ServiceClient;
|
||||
use App\Services\GoogleSheetService;
|
||||
use App\Models\DataSetting;
|
||||
use App\Models\PbgTaskGoogleSheet;
|
||||
|
||||
class ServiceSIMBG
|
||||
{
|
||||
use GlobalApiResponse;
|
||||
private $email;
|
||||
private $password;
|
||||
private $simbg_host;
|
||||
private $fetch_per_page;
|
||||
private $service_client;
|
||||
private $googleSheetService;
|
||||
/**
|
||||
* Create a new class instance.
|
||||
*/
|
||||
public function __construct(GoogleSheetService $googleSheetService)
|
||||
{
|
||||
$settings = GlobalSetting::whereIn('key', [
|
||||
'SIMBG_EMAIL', 'SIMBG_PASSWORD', 'SIMBG_HOST', 'FETCH_PER_PAGE'
|
||||
])->pluck('value', 'key');
|
||||
|
||||
$this->email = trim((string) ($settings['SIMBG_EMAIL'] ?? ""));
|
||||
$this->password = trim((string) ($settings['SIMBG_PASSWORD'] ?? ""));
|
||||
$this->simbg_host = trim((string) ($settings['SIMBG_HOST'] ?? ""));
|
||||
$this->fetch_per_page = trim((string) ($settings['FETCH_PER_PAGE'] ?? ""));
|
||||
|
||||
$this->service_client = new ServiceClient($this->simbg_host);
|
||||
$this->googleSheetService = $googleSheetService;
|
||||
}
|
||||
|
||||
public function getToken(){
|
||||
try{
|
||||
$url = "/api/user/v1/auth/login/";
|
||||
$body = [
|
||||
'email' => $this->email,
|
||||
'password' => $this->password,
|
||||
];
|
||||
|
||||
$res = $this->service_client->post($url, $body);
|
||||
if(!$res->original['success']){
|
||||
Log::error("Token not retrieved ", ['response' => $res]);
|
||||
throw new Exception("Token not retrieved.");
|
||||
}
|
||||
return $res;
|
||||
}catch(Exception $e){
|
||||
Log::error("Error on method get token ", ['response' => $e->getMessage()]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function syncIndexIntegration($uuids)
|
||||
{
|
||||
try{
|
||||
if(empty($uuids)){
|
||||
return false;
|
||||
}
|
||||
|
||||
$initResToken = $this->getToken();
|
||||
if (empty($initResToken->original['data']['token']['access'])) {
|
||||
Log::error("API response indicates failure", ['token' => 'Failed to retrieve token']);
|
||||
return false;
|
||||
}
|
||||
$token = $initResToken->original['data']['token']['access'];
|
||||
|
||||
$integrations = [];
|
||||
foreach($uuids as $uuid){
|
||||
$url = "/api/pbg/v1/detail/" . $uuid . "/retribution/indeks-terintegrasi/";
|
||||
|
||||
$headers = [
|
||||
'Authorization' => "Bearer " . $token,
|
||||
];
|
||||
|
||||
$res = $this->service_client->get($url, $headers);
|
||||
|
||||
if (empty($res->original['success']) || !$res->original['success']) {
|
||||
// Log error
|
||||
Log::error("API response indicates failure", ['url' => $url, 'uuid' => $uuid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = $res->original['data']['data'] ?? null;
|
||||
if (!$data) {
|
||||
Log::error("No valid data returned from API", ['url' => $url, 'uuid' => $uuid]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$integrations[] = [
|
||||
'pbg_task_uid' => $uuid,
|
||||
'indeks_fungsi_bangunan' => $data['indeks_fungsi_bangunan'] ?? null,
|
||||
'indeks_parameter_kompleksitas' => $data['indeks_parameter_kompleksitas'] ?? null,
|
||||
'indeks_parameter_permanensi' => $data['indeks_parameter_permanensi'] ?? null,
|
||||
'indeks_parameter_ketinggian' => $data['indeks_parameter_ketinggian'] ?? null,
|
||||
'faktor_kepemilikan' => $data['faktor_kepemilikan'] ?? null,
|
||||
'indeks_terintegrasi' => $data['indeks_terintegrasi'] ?? null,
|
||||
'total' => $data['total'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
PbgTaskIndexIntegrations::upsert($integrations, ['pbg_task_uid'], ['indeks_fungsi_bangunan',
|
||||
'indeks_parameter_kompleksitas', 'indeks_parameter_permanensi', 'indeks_parameter_ketinggian', 'faktor_kepemilikan', 'indeks_terintegrasi', 'total']);
|
||||
|
||||
return true;
|
||||
}catch (Exception $e){
|
||||
Log::error('error when sync index integration ', ['index integration'=> $e->getMessage()]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function syncTaskPBG()
|
||||
{
|
||||
try {
|
||||
Log::info("Processing google sheet sync");
|
||||
$importDatasource = ImportDatasource::create([
|
||||
'status' => ImportDatasourceStatus::Processing->value,
|
||||
]);
|
||||
|
||||
// sync google sheet first
|
||||
$totalRowCount = $this->googleSheetService->getLastRowByColumn("C");
|
||||
$sheetData = $this->googleSheetService->getSheetDataCollection($totalRowCount);
|
||||
$sheet_big_data = $this->googleSheetService->get_data_by_sheet();
|
||||
$data_setting_result = []; // Initialize result storage
|
||||
|
||||
$found_section = null; // Track which section is found
|
||||
|
||||
foreach ($sheet_big_data as $row) {
|
||||
// Check for section headers
|
||||
if (in_array("•PROSES PENERBITAN:", $row)) {
|
||||
$found_section = "MENUNGGU_KLIK_DPMPTSP";
|
||||
} elseif (in_array("•BERKAS AKTUAL TERVERIFIKASI DINAS TEKNIS 2024:", $row)) {
|
||||
$found_section = "REALISASI_TERBIT_PBG";
|
||||
} elseif (in_array("•TERPROSES DI DPUTR: belum selesai rekomtek'", $row)) {
|
||||
$found_section = "PROSES_DINAS_TEKNIS";
|
||||
}
|
||||
|
||||
// If a section is found and we reach "Grand Total", save the corresponding values
|
||||
if ($found_section && isset($row[0]) && trim($row[0]) === "Grand Total") {
|
||||
if ($found_section === "MENUNGGU_KLIK_DPMPTSP") {
|
||||
$data_setting_result["MENUNGGU_KLIK_DPMPTSP_COUNT"] = $this->convertToInteger($row[2]) ?? null;
|
||||
$data_setting_result["MENUNGGU_KLIK_DPMPTSP_SUM"] = $this->convertToDecimal($row[3]) ?? null;
|
||||
} elseif ($found_section === "REALISASI_TERBIT_PBG") {
|
||||
$data_setting_result["REALISASI_TERBIT_PBG_COUNT"] = $this->convertToInteger($row[2]) ?? null;
|
||||
$data_setting_result["REALISASI_TERBIT_PBG_SUM"] = $this->convertToDecimal($row[4]) ?? null;
|
||||
} elseif ($found_section === "PROSES_DINAS_TEKNIS") {
|
||||
$data_setting_result["PROSES_DINAS_TEKNIS_COUNT"] = $this->convertToInteger($row[2]) ?? null;
|
||||
$data_setting_result["PROSES_DINAS_TEKNIS_SUM"] = $this->convertToDecimal($row[3]) ?? null;
|
||||
}
|
||||
|
||||
// Reset section tracking after capturing "Grand Total"
|
||||
$found_section = null;
|
||||
}
|
||||
}
|
||||
|
||||
Log::info("data setting result", ['result' => $data_setting_result]);
|
||||
|
||||
foreach ($data_setting_result as $key => $value) {
|
||||
DataSetting::updateOrInsert(
|
||||
["key" => $key], // Find by key
|
||||
["value" => $value] // Update or insert value
|
||||
);
|
||||
}
|
||||
$mapToUpsert = [];
|
||||
|
||||
foreach ($sheetData as $data) {
|
||||
$mapToUpsert[] = [
|
||||
'no_registrasi' => $this->cleanString($data['no__registrasi'] ?? null),
|
||||
'jenis_konsultasi' => $this->cleanString($data['jenis_konsultasi'] ?? null),
|
||||
'fungsi_bg' => $this->cleanString($data['fungsi_bg'] ?? null),
|
||||
'tgl_permohonan' => $this->convertToDate($this->cleanString($data['tgl_permohonan'] ?? null)),
|
||||
'status_verifikasi' => $this->cleanString($data['status_verifikasi'] ?? null),
|
||||
'status_permohonan' => $this->convertToDate($this->cleanString($data['status_permohonan'] ?? null)),
|
||||
'alamat_pemilik' => $this->cleanString($data['alamat_pemilik'] ?? null),
|
||||
'no_hp' => $this->cleanString($data['no__hp'] ?? null),
|
||||
'email' => $this->cleanString($data['e_mail'] ?? null),
|
||||
'tanggal_catatan' => $this->convertToDate($this->cleanString($data['tanggal_catatan'] ?? null)),
|
||||
'catatan_kekurangan_dokumen' => $this->cleanString($data['catatan_kekurangan_dokumen'] ?? null),
|
||||
'gambar' => $this->cleanString($data['gambar'] ?? null),
|
||||
'krk_kkpr' => $this->cleanString($data['krk_kkpr'] ?? null),
|
||||
'no_krk' => $this->cleanString($data['no__krk'] ?? null),
|
||||
'lh' => $this->cleanString($data['lh'] ?? null),
|
||||
'ska' => $this->cleanString($data['ska'] ?? null),
|
||||
'keterangan' => $this->cleanString($data['keterangan'] ?? null),
|
||||
'helpdesk' => $this->cleanString($data['helpdesk'] ?? null),
|
||||
'pj' => $this->cleanString($data['pj'] ?? null),
|
||||
'kepemilikan' => $this->cleanString($data['kepemilikan'] ?? null),
|
||||
'potensi_taru' => $this->cleanString($data['potensi_taru'] ?? null),
|
||||
'validasi_dinas' => $this->cleanString($data['validasi_dinas'] ?? null),
|
||||
'kategori_retribusi' => $this->cleanString($data['kategori_retribusi'] ?? null),
|
||||
'no_urut_ba_tpt' => $this->cleanString($data['no__urut_ba_tpt__2024_0001_'] ?? null),
|
||||
'tanggal_ba_tpt' => $this->convertToDate($this->cleanString($data['tanggal_ba_tpt'] ?? null)),
|
||||
'no_urut_ba_tpa' => $this->cleanString($data['no__urut_ba_tpa'] ?? null),
|
||||
'tanggal_ba_tpa' => $this->convertToDate($this->cleanString($data['tanggal_ba_tpa'] ?? null)),
|
||||
'no_urut_skrd' => $this->cleanString($data['no__urut_skrd__2024_0001_'] ?? null),
|
||||
'tanggal_skrd' => $this->convertToDate($this->cleanString($data['tanggal_skrd'] ?? null)),
|
||||
'ptsp' => $this->cleanString($data['ptsp'] ?? null),
|
||||
'selesai_terbit' => $this->cleanString($data['selesai_terbit'] ?? null),
|
||||
'tanggal_pembayaran' => $this->convertToDate($this->cleanString($data['tanggal_pembayaran__yyyy_mm_dd_'] ?? null)),
|
||||
'format_sts' => $this->cleanString($data['format_sts'] ?? null),
|
||||
'tahun_terbit' => (int) ($data['tahun_terbit'] ?? null),
|
||||
'tahun_berjalan' => (int) ($data['tahun_berjalan'] ?? null),
|
||||
'kelurahan' => $this->cleanString($data['kelurahan'] ?? null),
|
||||
'kecamatan' => $this->cleanString($data['kecamatan'] ?? null),
|
||||
'lb' => $this->convertToDecimal($data['lb'] ?? null),
|
||||
'tb' => $this->convertToDecimal($data['tb'] ?? null),
|
||||
'jlb' => (int) ($data['jlb'] ?? null),
|
||||
'unit' => (int) ($data['unit'] ?? null),
|
||||
'usulan_retribusi' => (int) ($data['usulan_retribusi'] ?? null),
|
||||
'nilai_retribusi_keseluruhan_simbg' => $this->convertToDecimal($data['nilai_retribusi_keseluruhan__simbg_'] ?? null),
|
||||
'nilai_retribusi_keseluruhan_pad' => $this->convertToDecimal($data['nilai_retribusi_keseluruhan__pad_'] ?? null),
|
||||
'denda' => $this->convertToDecimal($data['denda'] ?? null),
|
||||
'latitude' => $this->cleanString($data['latitude'] ?? null),
|
||||
'longitude' => $this->cleanString($data['longitude'] ?? null),
|
||||
'nik_nib' => $this->cleanString($data['nik_nib'] ?? null),
|
||||
'dok_tanah' => $this->cleanString($data['dok__tanah'] ?? null),
|
||||
'temuan' => $this->cleanString($data['temuan'] ?? null),
|
||||
];
|
||||
}
|
||||
|
||||
$batchSize = 1000;
|
||||
$chunks = array_chunk($mapToUpsert, $batchSize);
|
||||
|
||||
foreach($chunks as $chunk){
|
||||
PbgTaskGoogleSheet::upsert($chunk, ["no_registrasi"],[
|
||||
'jenis_konsultasi',
|
||||
'nama_pemilik',
|
||||
'lokasi_bg',
|
||||
'fungsi_bg',
|
||||
'nama_bangunan',
|
||||
'tgl_permohonan',
|
||||
'status_verifikasi',
|
||||
'status_permohonan',
|
||||
'alamat_pemilik',
|
||||
'no_hp',
|
||||
'email',
|
||||
'tanggal_catatan',
|
||||
'catatan_kekurangan_dokumen',
|
||||
'gambar',
|
||||
'krk_kkpr',
|
||||
'no_krk',
|
||||
'lh',
|
||||
'ska',
|
||||
'keterangan',
|
||||
'helpdesk',
|
||||
'pj',
|
||||
'kepemilikan',
|
||||
'potensi_taru',
|
||||
'validasi_dinas',
|
||||
'kategori_retribusi',
|
||||
'no_urut_ba_tpt',
|
||||
'tanggal_ba_tpt',
|
||||
'no_urut_ba_tpa',
|
||||
'tanggal_ba_tpa',
|
||||
'no_urut_skrd',
|
||||
'tanggal_skrd',
|
||||
'ptsp',
|
||||
'selesai_terbit',
|
||||
'tanggal_pembayaran',
|
||||
'format_sts',
|
||||
'tahun_terbit',
|
||||
'tahun_berjalan',
|
||||
'kelurahan',
|
||||
'kecamatan',
|
||||
'lb',
|
||||
'tb',
|
||||
'jlb',
|
||||
'unit',
|
||||
'usulan_retribusi',
|
||||
'nilai_retribusi_keseluruhan_simbg',
|
||||
'nilai_retribusi_keseluruhan_pad',
|
||||
'denda',
|
||||
'latitude',
|
||||
'longitude',
|
||||
'nik_nib',
|
||||
'dok_tanah',
|
||||
'temuan',
|
||||
]);
|
||||
}
|
||||
|
||||
$initResToken = $this->getToken();
|
||||
if (empty($initResToken->original['data']['token']['access'])) {
|
||||
$importDatasource->update([
|
||||
'status' => ImportDatasourceStatus::Failed->value,
|
||||
'response_body' => 'Failed to retrieve token'
|
||||
]);
|
||||
return $this->resError("Failed to retrieve token");
|
||||
}
|
||||
$apiToken = $initResToken->original['data']['token']['access'];
|
||||
$headers = ['Authorization' => "Bearer " . $apiToken];
|
||||
|
||||
$url = "/api/pbg/v1/list/?page=1&size={$this->fetch_per_page}&sort=ASC";
|
||||
$initialResponse = $this->service_client->get($url, $headers);
|
||||
|
||||
$totalPage = $initialResponse->original['data']['total_page'] ?? 0;
|
||||
if ($totalPage == 0) {
|
||||
$importDatasource->update([
|
||||
'status' => ImportDatasourceStatus::Failed->value,
|
||||
'response_body' => 'Invalid response: no total_page'
|
||||
]);
|
||||
return $this->resError("Invalid response from API");
|
||||
}
|
||||
|
||||
$savedCount = $failedCount = 0;
|
||||
|
||||
Log::info("Fetching tasks", ['total page' => $totalPage]);
|
||||
|
||||
for ($currentPage = 1; $currentPage <= $totalPage; $currentPage++) {
|
||||
try {
|
||||
$pageUrl = "/api/pbg/v1/list/?page={$currentPage}&size={$this->fetch_per_page}&sort=ASC";
|
||||
|
||||
Log::info("Fetching tasks", ['currentPage' => $currentPage]);
|
||||
$headers = [
|
||||
'Authorization' => "Bearer " . $apiToken, // Update headers
|
||||
];
|
||||
|
||||
for ($attempt = 0; $attempt < 2; $attempt++) { // Try twice (original + retry)
|
||||
|
||||
$response = $this->service_client->get($pageUrl, $headers);
|
||||
|
||||
if ($response instanceof \Illuminate\Http\JsonResponse) {
|
||||
$decodedResponse = json_decode($response->getContent(), true);
|
||||
|
||||
if (isset($decodedResponse['errors']['code']) && $decodedResponse['errors']['code'] === 'token_not_valid') {
|
||||
$initResToken = $this->getToken();
|
||||
|
||||
if (!empty($initResToken->original['data']['token']['access'])) {
|
||||
$new_token = $initResToken->original['data']['token']['access'];
|
||||
$headers['Authorization'] = "Bearer " . $new_token;
|
||||
continue;
|
||||
} else {
|
||||
Log::error("Failed to refresh token");
|
||||
return $this->resError("Failed to refresh token");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Success case, break loop
|
||||
break;
|
||||
}
|
||||
|
||||
$tasks = $response->original['data']['data'] ?? [];
|
||||
|
||||
if (empty($tasks)) {
|
||||
Log::warning("No data found on page", ['page' => $currentPage]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$tasksCollective = [];
|
||||
foreach ($tasks as $item) {
|
||||
try {
|
||||
$tasksCollective[] = [
|
||||
'uuid' => $item['uid'],
|
||||
'name' => $item['name'],
|
||||
'owner_name' => $item['owner_name'],
|
||||
'application_type' => $item['application_type'],
|
||||
'application_type_name' => $item['application_type_name'],
|
||||
'condition' => $item['condition'],
|
||||
'registration_number' => $item['registration_number'],
|
||||
'document_number' => $item['document_number'],
|
||||
'address' => $item['address'],
|
||||
'status' => $item['status'],
|
||||
'status_name' => $item['status_name'],
|
||||
'slf_status' => $item['slf_status'] ?? null,
|
||||
'slf_status_name' => $item['slf_status_name'] ?? null,
|
||||
'function_type' => $item['function_type'],
|
||||
'consultation_type' => $item['consultation_type'],
|
||||
'due_date' => $item['due_date'],
|
||||
'land_certificate_phase' => $item['land_certificate_phase'],
|
||||
'task_created_at' => isset($item['created_at']) ? Carbon::parse($item['created_at'])->format('Y-m-d H:i:s') : null,
|
||||
'updated_at' => now(),
|
||||
'created_at' => now(),
|
||||
];
|
||||
|
||||
$this->syncTaskDetailSubmit($item['uid'], $apiToken);
|
||||
$this->syncTaskAssignments($item['uid']);
|
||||
$savedCount++;
|
||||
} catch (Exception $e) {
|
||||
$failedCount++;
|
||||
Log::error("Failed to process task", [
|
||||
'error' => $e->getMessage(),
|
||||
'task' => $item,
|
||||
]);
|
||||
continue; // Skip failed task, continue processing the rest
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($tasksCollective)) {
|
||||
PbgTask::upsert($tasksCollective, ['uuid'], [
|
||||
'name', 'owner_name', 'application_type', 'application_type_name', 'condition',
|
||||
'registration_number', 'document_number', 'address', 'status', 'status_name',
|
||||
'slf_status', 'slf_status_name', 'function_type', 'consultation_type', 'due_date',
|
||||
'land_certificate_phase', 'task_created_at', 'updated_at'
|
||||
]);
|
||||
|
||||
$uuids = array_column($tasksCollective, 'uuid');
|
||||
$this->syncIndexIntegration($uuids);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::error("Failed to process page", [
|
||||
'error' => $e->getMessage(),
|
||||
'page' => $currentPage,
|
||||
]);
|
||||
continue; // Skip the failed page and move to the next
|
||||
}
|
||||
}
|
||||
|
||||
BigdataResume::generateResumeData($importDatasource->id, date('Y'), "simbg");
|
||||
|
||||
// Final update after processing all pages
|
||||
$importDatasource->update([
|
||||
'status' => ImportDatasourceStatus::Success->value,
|
||||
'message' => "Successfully processed: $savedCount, Failed: $failedCount"
|
||||
]);
|
||||
|
||||
Log::info("syncTaskList completed", ['savedCount' => $savedCount, 'failedCount' => $failedCount]);
|
||||
|
||||
return $this->resSuccess(['savedCount' => $savedCount, 'failedCount' => $failedCount]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error("syncTaskList failed", ['error' => $e->getMessage()]);
|
||||
if (isset($importDatasource)) {
|
||||
$importDatasource->update([
|
||||
'status' => ImportDatasourceStatus::Failed->value,
|
||||
'response_body' => 'Critical failure: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
return $this->resError("Critical failure occurred: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function syncTaskDetailSubmit($uuid, $token)
|
||||
{
|
||||
try{
|
||||
$url = "/api/pbg/v1/detail/" . $uuid . "/retribution/submit/";
|
||||
|
||||
$headers = [
|
||||
'Authorization' => "Bearer " . $token,
|
||||
];
|
||||
|
||||
for ($attempt = 0; $attempt < 2; $attempt++) {
|
||||
$res = $this->service_client->get($url, $headers);
|
||||
|
||||
// Check if response is JsonResponse and decode it
|
||||
if ($res instanceof \Illuminate\Http\JsonResponse) {
|
||||
$decodedResponse = json_decode($res->getContent(), true);
|
||||
|
||||
if (isset($decodedResponse['errors']['code']) && $decodedResponse['errors']['code'] === 'token_not_valid') {
|
||||
$initResToken = $this->getToken();
|
||||
|
||||
if (!empty($initResToken->original['data']['token']['access'])) {
|
||||
$new_token = $initResToken->original['data']['token']['access'];
|
||||
|
||||
$headers['Authorization'] = "Bearer " . $new_token;
|
||||
continue;
|
||||
} else {
|
||||
Log::error("Failed to refresh token");
|
||||
return $this->resError("Failed to refresh token");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure response is valid before accessing properties
|
||||
$responseData = $res->original ?? [];
|
||||
$data = $responseData['data']['data'] ?? [];
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$detailCreatedAt = isset($data['created_at'])
|
||||
? Carbon::parse($data['created_at'])->format('Y-m-d H:i:s')
|
||||
: null;
|
||||
|
||||
$detailUpdatedAt = isset($data['updated_at'])
|
||||
? Carbon::parse($data['updated_at'])->format('Y-m-d H:i:s')
|
||||
: null;
|
||||
|
||||
$pbg_task_retributions = PbgTaskRetributions::updateOrCreate(
|
||||
['detail_id' => $data['id']],
|
||||
[
|
||||
'detail_uid' => $data['uid'] ?? null,
|
||||
'detail_created_at' => $detailCreatedAt ?? null,
|
||||
'detail_updated_at' => $detailUpdatedAt ?? null,
|
||||
'luas_bangunan' => $data['luas_bangunan'] ?? null,
|
||||
'indeks_lokalitas' => $data['indeks_lokalitas'] ?? null,
|
||||
'wilayah_shst' => $data['wilayah_shst'] ?? null,
|
||||
'kegiatan_id' => $data['kegiatan']['id'] ?? null,
|
||||
'kegiatan_name' => $data['kegiatan']['name'] ?? null,
|
||||
'nilai_shst' => $data['nilai_shst'] ?? null,
|
||||
'indeks_terintegrasi' => $data['indeks_terintegrasi'] ?? null,
|
||||
'indeks_bg_terbangun' => $data['indeks_bg_terbangun'] ?? null,
|
||||
'nilai_retribusi_bangunan' => $data['nilai_retribusi_bangunan'] ?? null,
|
||||
'nilai_prasarana' => $data['nilai_prasarana'] ?? null,
|
||||
'created_by' => $data['created_by'] ?? null,
|
||||
'pbg_document' => $data['pbg_document'] ?? null,
|
||||
'underpayment' => $data['underpayment'] ?? null,
|
||||
'skrd_amount' => $data['skrd_amount'] ?? null,
|
||||
'pbg_task_uid' => $uuid,
|
||||
]
|
||||
);
|
||||
|
||||
$pbg_task_retribution_id = $pbg_task_retributions->id;
|
||||
|
||||
$prasaranaData = $data['prasarana'] ?? [];
|
||||
if (!empty($prasaranaData)) {
|
||||
$insertData = array_map(fn($item) => [
|
||||
'pbg_task_uid' => $uuid,
|
||||
'pbg_task_retribution_id' => $pbg_task_retribution_id,
|
||||
'prasarana_id' => $item['id'] ?? null,
|
||||
'prasarana_type' => $item['prasarana_type'] ?? null,
|
||||
'building_type' => $item['building_type'] ?? null,
|
||||
'total' => $item['total'] ?? null,
|
||||
'quantity' => $item['quantity'] ?? null,
|
||||
'unit' => $item['unit'] ?? null,
|
||||
'index_prasarana' => $item['index_prasarana'] ?? null,
|
||||
], $prasaranaData);
|
||||
|
||||
// Use bulk insert or upsert for faster database operation
|
||||
PbgTaskPrasarana::upsert($insertData, ['prasarana_id']);
|
||||
}
|
||||
return true;
|
||||
|
||||
}catch(Exception $e){
|
||||
Log::error("Failed to sync task detail submit", ['error' => $e->getMessage(), 'uuid' => $uuid]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function syncTaskAssignments($uuid){
|
||||
try{
|
||||
$init_token = $this->getToken();
|
||||
$token = $init_token->original['data']['token']['access'];
|
||||
$url = "/api/pbg/v1/list-tim-penilai/". $uuid . "/?page=1&size=10";
|
||||
$headers = [
|
||||
'Authorization' => "Bearer " . $token,
|
||||
];
|
||||
|
||||
$response = $this->service_client->get($url, $headers);
|
||||
$datas = $response->original['data']['data'] ?? [];
|
||||
if(empty($datas)){
|
||||
return false;
|
||||
}
|
||||
$task_assignments = [];
|
||||
|
||||
foreach ($datas as $data) {
|
||||
$task_assignments[] = [
|
||||
'pbg_task_uid' => $uuid,
|
||||
'user_id' => $data['user_id'],
|
||||
'name' => $data['name'],
|
||||
'username' => $data['username'],
|
||||
'email' => $data['email'],
|
||||
'phone_number' => $data['phone_number'],
|
||||
'role' => $data['role'],
|
||||
'role_name' => $data['role_name'],
|
||||
'is_active' => $data['is_active'],
|
||||
'file' => !empty($data['file']) ? json_encode($data['file']) : null,
|
||||
'expertise' => !empty($data['expertise']) ? json_encode($data['expertise']) : null,
|
||||
'experience' => !empty($data['experience']) ? json_encode($data['experience']) : null,
|
||||
'is_verif' => $data['is_verif'],
|
||||
'uid' => $data['uid'],
|
||||
'status' => $data['status'],
|
||||
'status_name' => $data['status_name'],
|
||||
'note' => $data['note'],
|
||||
'ta_id' => $data['id'],
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
}
|
||||
TaskAssignment::upsert(
|
||||
$task_assignments,
|
||||
['uid'],
|
||||
['ta_id','name', 'username', 'email', 'phone_number', 'role', 'role_name', 'is_active', 'file', 'expertise', 'experience', 'is_verif', 'status', 'status_name', 'note', 'updated_at']
|
||||
);
|
||||
return true;
|
||||
}catch(Exception $e){
|
||||
Log::error("Failed to sync task assignments", ['error' => $e->getMessage()]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
protected function convertToDecimal(?string $value): ?float
|
||||
{
|
||||
if (empty($value)) {
|
||||
return null; // Return null if the input is empty
|
||||
}
|
||||
|
||||
// Remove all non-numeric characters except comma and dot
|
||||
$value = preg_replace('/[^0-9,\.]/', '', $value);
|
||||
|
||||
// If the number contains both dot (.) and comma (,)
|
||||
if (strpos($value, '.') !== false && strpos($value, ',') !== false) {
|
||||
$value = str_replace('.', '', $value); // Remove thousands separator
|
||||
$value = str_replace(',', '.', $value); // Convert decimal separator to dot
|
||||
}
|
||||
// If only a dot is present (assumed as thousands separator)
|
||||
elseif (strpos($value, '.') !== false) {
|
||||
$value = str_replace('.', '', $value); // Remove all dots (treat as thousands separators)
|
||||
}
|
||||
// If only a comma is present (assumed as decimal separator)
|
||||
elseif (strpos($value, ',') !== false) {
|
||||
$value = str_replace(',', '.', $value); // Convert comma to dot (decimal separator)
|
||||
}
|
||||
|
||||
// Ensure the value is numeric before returning
|
||||
return is_numeric($value) ? (float) number_format((float) $value, 2, '.', '') : null;
|
||||
}
|
||||
|
||||
protected function convertToInteger($value) {
|
||||
// Check if the value is an empty string, and return null if true
|
||||
if (trim($value) === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cleaned = str_replace('.','', $value);
|
||||
|
||||
// Otherwise, cast to integer
|
||||
return (int) $cleaned;
|
||||
}
|
||||
|
||||
protected function convertToDate($dateString)
|
||||
{
|
||||
try {
|
||||
// Check if the string is empty
|
||||
if (empty($dateString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to parse the date string
|
||||
$date = Carbon::parse($dateString);
|
||||
|
||||
// Return the Carbon instance
|
||||
return $date->format('Y-m-d');
|
||||
} catch (Exception $e) {
|
||||
// Return null if an error occurs during parsing
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanString($value)
|
||||
{
|
||||
return isset($value) ? trim(strip_tags($value)) : null;
|
||||
}
|
||||
}
|
||||
@@ -246,8 +246,6 @@ class ServiceTabPbgTask
|
||||
|
||||
$data = $responseData['data'];
|
||||
|
||||
Log::info("Executed uid : {$uuid}");
|
||||
|
||||
// Use the static method from PbgTaskDetail model to create/update
|
||||
PbgStatus::createOrUpdateFromApi($data, $uuid);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class UsersRoleMenuSeeder extends Seeder
|
||||
// Fetch all menus in a single query and index by name
|
||||
$menus = Menu::whereIn('name', [
|
||||
'Dashboard', 'Master', 'Settings', 'Data Settings', 'Data', 'Laporan', 'Neng Bedas',
|
||||
'Approval', 'Tools', 'Dashboard PBG', 'Users', 'Syncronize',
|
||||
'Approval', 'Tools', 'Users', 'Syncronize', 'Dashboard Pimpinan (SIMBG)',
|
||||
'Menu', 'Role', 'Setting Dashboard', 'PBG', 'Reklame', 'Usaha atau Industri', 'Pariwisata',
|
||||
'Lap Pariwisata', 'UMKM', 'Dashboard Potensi', 'Tata Ruang', 'PDAM', 'PETA',
|
||||
'Lap Pimpinan', 'Dalam Sistem', 'Luar Sistem', 'Google Sheets', 'TPA TPT', 'Pajak',
|
||||
@@ -32,14 +32,14 @@ class UsersRoleMenuSeeder extends Seeder
|
||||
$permissions = [
|
||||
'superadmin' => [
|
||||
'Dashboard', 'Master', 'Settings', 'Data Settings', 'Data', 'Laporan', 'Neng Bedas',
|
||||
'Approval', 'Tools', 'Dashboard PBG', 'Users', 'Syncronize',
|
||||
'Approval', 'Tools', 'Users', 'Syncronize', 'Dashboard Pimpinan (SIMBG)',
|
||||
'Menu', 'Role', 'Setting Dashboard', 'PBG', 'Reklame', 'Usaha atau Industri', 'Pariwisata',
|
||||
'Lap Pariwisata', 'UMKM', 'Dashboard Potensi', 'Tata Ruang', 'PDAM', 'Dalam Sistem',
|
||||
'Luar Sistem', 'Lap Pimpinan', 'Google Sheets', 'TPA TPT', 'Approval Pejabat',
|
||||
'Undangan', 'Rekap Pembayaran', 'Lap Rekap Data Pembayaran', 'Lap PBG (PTSP)', 'Lap Pertumbuhan', 'Pajak'
|
||||
],
|
||||
'user' => ['Dashboard', 'Data', 'Laporan', 'Neng Bedas',
|
||||
'Approval', 'Tools', 'Dashboard PBG', 'Users', 'Syncronize',
|
||||
'Approval', 'Tools', 'Users', 'Syncronize', 'Dashboard Pimpinan (SIMBG)',
|
||||
'Menu', 'Role', 'Setting Dashboard', 'PBG', 'Reklame', 'Usaha atau Industri', 'Pariwisata',
|
||||
'Lap Pariwisata', 'UMKM', 'Dashboard Potensi', 'Tata Ruang', 'PDAM', 'Dalam Sistem',
|
||||
'Luar Sistem', 'Lap Pimpinan', 'Google Sheets', 'TPA TPT', 'Approval Pejabat',
|
||||
|
||||
@@ -216,7 +216,9 @@ class SyncronizeTask {
|
||||
})
|
||||
.then((data) => {
|
||||
this.toastMessage.innerText =
|
||||
data.data.message || "Synchronize successfully!";
|
||||
data?.data?.message ||
|
||||
data?.message ||
|
||||
"Synchronize successfully!";
|
||||
this.toast.show();
|
||||
|
||||
// Update the table if it exists
|
||||
|
||||
@@ -17,10 +17,22 @@
|
||||
<li class="menu-title">Menu</li>
|
||||
|
||||
@php
|
||||
// Menentukan apakah sebuah menu (atau anaknya) aktif berdasarkan request('menu_id')
|
||||
function isActiveMenu($menu, $currentId) {
|
||||
if (!$currentId) return false;
|
||||
if ((string)$menu->id === (string)$currentId) return true;
|
||||
foreach ($menu->children as $child) {
|
||||
if (isActiveMenu($child, $currentId)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function renderMenu($menus) {
|
||||
$currentMenuId = request('menu_id');
|
||||
foreach ($menus as $menu) {
|
||||
$collapseId = "sidebar-" . $menu->id;
|
||||
$hasChildren = $menu->children->count() > 0;
|
||||
$isActive = isActiveMenu($menu, $currentMenuId);
|
||||
|
||||
// Pastikan route tersedia dan boleh ditampilkan
|
||||
$menuUrl = '#';
|
||||
@@ -28,14 +40,14 @@
|
||||
if (Route::has($menu->url)) {
|
||||
$menuUrl = route($menu->url, ['menu_id' => $menu->id]);
|
||||
} else {
|
||||
$menuUrl = $menu->url . '?menu_id=' . $menu->id;
|
||||
$menuUrl = $menu->url . (strpos($menu->url, '?') !== false ? '&' : '?') . 'menu_id=' . $menu->id;
|
||||
}
|
||||
}
|
||||
|
||||
echo '<li class="nav-item ' . ($hasChildren ? 'has-children' : '') . '">';
|
||||
echo '<a class="nav-link ' . ($hasChildren ? 'menu-arrow' : '') . '"
|
||||
echo '<li class="nav-item ' . ($hasChildren ? 'has-children' : '') . ' ' . ($isActive ? 'active' : '') . '">';
|
||||
echo '<a class="nav-link ' . ($hasChildren ? 'menu-arrow' : '') . ' ' . ($isActive ? 'active' : '') . '"
|
||||
href="' . ($hasChildren ? "#$collapseId" : $menuUrl) . '"
|
||||
' . ($hasChildren ? 'data-bs-toggle="collapse" role="button" aria-expanded="false" aria-controls="' . $collapseId . '"' : '') . '>';
|
||||
' . ($hasChildren ? 'data-bs-toggle="collapse" role="button" aria-expanded="' . ($isActive ? 'true' : 'false') . '" aria-controls="' . $collapseId . '"' : '') . '>';
|
||||
|
||||
// Tampilkan ikon hanya jika tersedia
|
||||
if (!empty($menu->icon)) {
|
||||
@@ -48,7 +60,7 @@
|
||||
echo '</a>';
|
||||
|
||||
if ($hasChildren) {
|
||||
echo '<div class="collapse" id="' . $collapseId . '">
|
||||
echo '<div class="collapse ' . ($isActive ? 'show' : '') . '" id="' . $collapseId . '">
|
||||
<ul class="nav sub-navbar-nav">';
|
||||
renderMenu($menu->children);
|
||||
echo '</ul></div>';
|
||||
@@ -72,4 +84,52 @@
|
||||
@for ($i = 0; $i < 20; $i++)
|
||||
<div class="shooting-star"></div>
|
||||
@endfor
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Sidebar hover/active contrast improvements */
|
||||
.app-sidebar .nav-link {
|
||||
transition: background-color .2s ease, color .2s ease;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
/* Hover state (dark green theme) */
|
||||
.app-sidebar .nav-link:hover {
|
||||
background-color: #eaf7f0; /* light green */
|
||||
color: #146c43; /* lighter dark green */
|
||||
}
|
||||
|
||||
/* Active state for parents and leaf items (dark green) */
|
||||
.app-sidebar .nav-item.active > .nav-link,
|
||||
.app-sidebar .nav-link.active {
|
||||
background-color: #198754; /* bootstrap success */
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Optional: subtle left border indicator on active */
|
||||
.app-sidebar .nav-item.active > .nav-link,
|
||||
.app-sidebar .sub-navbar-nav .nav-link.active {
|
||||
box-shadow: inset 4px 0 0 0 #146c43;
|
||||
}
|
||||
|
||||
/* Submenu links */
|
||||
.app-sidebar .sub-navbar-nav .nav-link:hover {
|
||||
background-color: #f1fbf5;
|
||||
color: #146c43;
|
||||
}
|
||||
|
||||
.app-sidebar .sub-navbar-nav .nav-link.active {
|
||||
background-color: #198754;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Keep icon color in sync */
|
||||
.app-sidebar .nav-link:hover .nav-icon iconify-icon,
|
||||
.app-sidebar .nav-item.active > .nav-link .nav-icon iconify-icon,
|
||||
.app-sidebar .nav-link.active .nav-icon iconify-icon,
|
||||
.app-sidebar .sub-navbar-nav .nav-link.active .nav-icon iconify-icon {
|
||||
color: currentColor;
|
||||
}
|
||||
</style>
|
||||
@@ -9,27 +9,9 @@
|
||||
class="fs-24 align-middle"></iconify-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- App Search-->
|
||||
<!-- <form class="app-search d-none d-md-block me-auto">
|
||||
<div class="position-relative">
|
||||
<input type="search" class="form-control" placeholder="admin,widgets..."
|
||||
autocomplete="off" value="">
|
||||
<iconify-icon icon="solar:magnifer-outline" class="search-widget-icon"></iconify-icon>
|
||||
</div>
|
||||
</form> -->
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<!-- Theme Color (Light/Dark) -->
|
||||
{{-- <div class="topbar-item">
|
||||
<button type="button" class="topbar-button" id="light-dark-mode">
|
||||
<iconify-icon icon="solar:moon-outline"
|
||||
class="fs-22 align-middle light-mode"></iconify-icon>
|
||||
<iconify-icon icon="solar:sun-2-outline"
|
||||
class="fs-22 align-middle dark-mode"></iconify-icon>
|
||||
</button>
|
||||
</div> --}}
|
||||
|
||||
<div class="topbar-item">
|
||||
<a href="{{ route('chatbot.index') }}" class="topbar-button">
|
||||
@@ -37,118 +19,6 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Notification -->
|
||||
{{-- <div class="dropdown topbar-item">
|
||||
<button type="button" class="topbar-button position-relative"
|
||||
id="page-header-notifications-dropdown" data-bs-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<iconify-icon icon="solar:bell-bing-outline" class="fs-22 align-middle"></iconify-icon>
|
||||
<span
|
||||
class="position-absolute topbar-badge fs-10 translate-middle badge bg-danger rounded-pill">5<span
|
||||
class="visually-hidden">unread messages</span></span>
|
||||
</button>
|
||||
<div class="dropdown-menu py-0 dropdown-lg dropdown-menu-end"
|
||||
aria-labelledby="page-header-notifications-dropdown">
|
||||
<div class="p-2 border-bottom bg-light bg-opacity-50">
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<h6 class="m-0 fs-16 fw-semibold"> Notifications (5)</h6>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="javascript: void(0);" class="text-dark text-decoration-underline">
|
||||
<small>Clear All</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-simplebar style="max-height: 250px;">
|
||||
<!-- Item -->
|
||||
<a href="javascript:void(0);" class="dropdown-item p-2 border-bottom text-wrap">
|
||||
<div class="d-flex">
|
||||
<div class="flex-shrink-0">
|
||||
<img src="/images/users/avatar-1.jpg"
|
||||
class="img-fluid me-2 avatar-sm rounded-circle" alt="avatar-1" />
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<p class="mb-0"><span class="fw-medium">Sally Bieber </span>started
|
||||
following you. Check out their profile!"</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Item -->
|
||||
<a href="javascript:void(0);" class="dropdown-item p-2 border-bottom">
|
||||
<div class="d-flex">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="avatar-sm me-2">
|
||||
<span
|
||||
class="avatar-title text-bg-info fw-semibold fs-20 rounded-circle">
|
||||
G
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<p class="mb-0 fw-medium">Gloria Chambers</p>
|
||||
<p class="mb-0 text-wrap">
|
||||
mentioned you in a comment: '@admin, check this out!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Item -->
|
||||
<a href="javascript:void(0);" class="dropdown-item p-2 border-bottom">
|
||||
<div class="d-flex">
|
||||
<div class="flex-shrink-0">
|
||||
<img src="/images/users/avatar-3.jpg"
|
||||
class="img-fluid me-2 avatar-sm rounded-circle" alt="avatar-3" />
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<p class="mb-0 fw-medium">Jacob Gines</p>
|
||||
<p class="mb-0 text-wrap">
|
||||
Answered to your comment on the cash flow forecast's graph 🔔.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Item -->
|
||||
<a href="javascript:void(0);" class="dropdown-item p-2 border-bottom">
|
||||
<div class="d-flex">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="avatar-sm me-2">
|
||||
<span
|
||||
class="avatar-title bg-soft-warning text-warning fs-20 rounded-circle">
|
||||
<iconify-icon icon="solar:leaf-outline"></iconify-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<p class="mb-0 fw-medium text-wrap">A new system update is available.
|
||||
Update now for the latest features.</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<!-- Item -->
|
||||
<a href="javascript:void(0);" class="dropdown-item p-2 border-bottom">
|
||||
<div class="d-flex">
|
||||
<div class="flex-shrink-0">
|
||||
<img src="/images/users/avatar-5.jpg"
|
||||
class="img-fluid me-2 avatar-sm rounded-circle" alt="avatar-5" />
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<p class="mb-0 fw-medium">Shawn Bunch</p>
|
||||
<p class="mb-0 text-wrap">
|
||||
commented on your post: 'Great photo!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-center p-2">
|
||||
<a href="javascript:void(0);" class="btn btn-primary btn-sm">View All Notification <i
|
||||
class="bx bx-right-arrow-alt ms-1"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div> --}}
|
||||
|
||||
<!-- User -->
|
||||
<div class="dropdown topbar-item">
|
||||
<a type="button" class="topbar-button" id="page-header-user-dropdown" data-bs-toggle="dropdown"
|
||||
@@ -162,28 +32,6 @@
|
||||
<!-- item-->
|
||||
<h6 class="dropdown-header">{{ Auth::user()->email }}</h6>
|
||||
|
||||
<!-- <a class="dropdown-item" href="#">
|
||||
<iconify-icon icon="solar:user-outline"
|
||||
class="align-middle me-2 fs-18"></iconify-icon><span class="align-middle">My
|
||||
Account</span>
|
||||
</a>
|
||||
|
||||
<a class="dropdown-item" href="#">
|
||||
<iconify-icon icon="solar:wallet-outline"
|
||||
class="align-middle me-2 fs-18"></iconify-icon><span
|
||||
class="align-middle">Pricing</span>
|
||||
</a>
|
||||
<a class="dropdown-item" href="#">
|
||||
<iconify-icon icon="solar:help-outline"
|
||||
class="align-middle me-2 fs-18"></iconify-icon><span
|
||||
class="align-middle">Help</span>
|
||||
</a>
|
||||
<a class="dropdown-item" href="auth-{{ route ('dashboard.home') }}">
|
||||
<iconify-icon icon="solar:lock-keyhole-outline"
|
||||
class="align-middle me-2 fs-18"></iconify-icon><span class="align-middle">Lock
|
||||
screen</span>
|
||||
</a> -->
|
||||
|
||||
<div class="dropdown-divider my-1"></div>
|
||||
|
||||
<form id="logout-form" action="{{route('logout')}}" method="POST" style="display: none;">
|
||||
@@ -200,12 +48,60 @@
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<style>
|
||||
/* Tampilkan hover submenu HANYA saat sidebar collapsed (berbagai kemungkinan class) */
|
||||
body.sidebar-collapsed .app-sidebar .navbar-nav > li.nav-item.has-children:hover > .collapse,
|
||||
.app-sidebar.collapsed .navbar-nav > li.nav-item.has-children:hover > .collapse,
|
||||
.app-sidebar.mini .navbar-nav > li.nav-item.has-children:hover > .collapse {
|
||||
display: block !important;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(100% + 8px);
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.08);
|
||||
padding: 6px 6px;
|
||||
min-width: 260px;
|
||||
width: clamp(260px, 40vw, 380px); /* responsive, bounded width */
|
||||
box-sizing: border-box;
|
||||
overflow: hidden; /* clip inner overflow to maintain box */
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.app-sidebar .sub-navbar-nav {
|
||||
width: 100%;
|
||||
max-width: 100%; /* pastikan nggak lebih besar dari box */
|
||||
}
|
||||
|
||||
.app-sidebar .sub-navbar-nav .nav-link,
|
||||
.app-sidebar .sub-navbar-nav .nav-link .nav-text {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
white-space: normal !important;
|
||||
word-break: break-word !important;
|
||||
overflow-wrap: break-word !important;
|
||||
}
|
||||
|
||||
.app-sidebar .sub-navbar-nav .nav-link:hover {
|
||||
background: #f1fbf5;
|
||||
color: #146c43;
|
||||
}
|
||||
|
||||
.app-sidebar .sub-navbar-nav .nav-link .nav-text {
|
||||
display: inline !important;
|
||||
visibility: visible !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* Do not force-hide parent nav-text in any state */
|
||||
</style>
|
||||
<script>
|
||||
function logoutUser() {
|
||||
// Hapus token dari localStorage
|
||||
localStorage.removeItem('token');
|
||||
|
||||
|
||||
// Submit form logout Laravel
|
||||
document.getElementById('logout-form').submit();
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
@@ -79,7 +79,6 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
|
||||
Route::get('/scraping','index')->name('scraping');
|
||||
Route::get('/retry-scraping/{id}','retry_syncjob')->name('retry-scraping');
|
||||
});
|
||||
// Route::apiResource('/scraping', ScrapingController::class);
|
||||
|
||||
// reklame
|
||||
Route::apiResource('advertisements', AdvertisementController::class);
|
||||
@@ -118,11 +117,6 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
|
||||
|
||||
// sync pbg google sheet
|
||||
Route::apiResource('/api-google-sheet', GoogleSheetController::class);
|
||||
Route::get('/sync-task', [SyncronizeController::class, 'syncPbgTask'])->name('api.task');
|
||||
Route::get('/get-user-token', [SyncronizeController::class, 'getUserToken'])->name('api.task.token');
|
||||
Route::get('/get-index-integration-retribution/{uuid}', [SyncronizeController::class, 'syncIndexIntegration'])->name('api.task.inntegration');
|
||||
Route::get('/sync-task-submit/{uuid}', [SyncronizeController::class, 'syncTaskDetailSubmit'])->name('api.task.submit');
|
||||
Route::get('/sync-task-assignments/{uuid}', [SyncronizeController::class, 'syncTaskAssignments'])->name('api.task.assignments');
|
||||
|
||||
// menus api
|
||||
Route::controller(MenusController::class)->group(function (){
|
||||
|
||||
@@ -8,6 +8,5 @@ Artisan::command('inspire', function () {
|
||||
$this->comment(Inspiring::quote());
|
||||
})->purpose('Display an inspiring quote')->hourly();
|
||||
|
||||
// Schedule::command("app:scraping-leader-data")->dailyAt("00:00");
|
||||
Schedule::command("app:start-scraping-data --confirm")->dailyAt("00:00");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user