Compare commits
23 Commits
fef6ae7522
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91085e8796 | ||
|
|
61e6eb9803 | ||
|
|
148dfebb4a | ||
|
|
aa34fff979 | ||
|
|
1a24b18719 | ||
|
|
e265e2ec35 | ||
|
|
e577da737b | ||
|
|
05ca927c38 | ||
|
|
fc4b419878 | ||
|
|
53d12d6798 | ||
|
|
809eb85255 | ||
|
|
8a513460bb | ||
|
|
fc74875cce | ||
|
|
beb7d935c9 | ||
|
|
5c4cebd2b3 | ||
|
|
cbe3d00c96 | ||
|
|
65d9247b46 | ||
|
|
63310f2748 | ||
|
|
c6257b79bf | ||
|
|
38493063c4 | ||
|
|
954b2d8716 | ||
|
|
41cfce589b | ||
|
|
8de1b51fea |
@@ -1,123 +0,0 @@
|
|||||||
PbgTask::truncate();
|
|
||||||
exit
|
|
||||||
(new App\Jobs\ScrapingDataJob())->handle();
|
|
||||||
(new App\Jobs\ScrapingDataJob())->handle();
|
|
||||||
(new App\Jobs\ScrapingDataJob())->handle();
|
|
||||||
exit
|
|
||||||
(new App\Jobs\ScrapingDataJob())->handle();
|
|
||||||
exit
|
|
||||||
(new App\Jobs\ScrapingDataJob())->handle();
|
|
||||||
(new App\Jobs\ScrapingDataJob())->handle();
|
|
||||||
exit
|
|
||||||
exit
|
|
||||||
BigdataResume::generateResumeData(253,2025,"simbg");
|
|
||||||
exit
|
|
||||||
BigdataResume::generateResumeData(253,2025,"simbg");
|
|
||||||
exit
|
|
||||||
Bigdataresume::generateResumeData(253,2025,'simbg');
|
|
||||||
BigdataResume::generateResumeData(253,2025,'simbg');
|
|
||||||
exit
|
|
||||||
BigdataResume::generateResumeData(253,2025,"simbg");
|
|
||||||
BigdataResume::generateResumeData(253,2025,"simbg");
|
|
||||||
exit
|
|
||||||
BigdataResume::generateResumeData(253,2025,"simbg");
|
|
||||||
exit
|
|
||||||
$importDatasource = \App\Models\ImportDatasource::create([
|
|
||||||
'message' => 'Testing BigdataResume Generation',
|
|
||||||
'status' => 'success',
|
|
||||||
'start_time' => now(),
|
|
||||||
'finish_time' => now()
|
|
||||||
]);
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
exit
|
|
||||||
$importDatasource = \App\Models\ImportDatasource::create([
|
|
||||||
'message' => 'Testing BigdataResume Generation',
|
|
||||||
'status' => 'success',
|
|
||||||
'start_time' => now(),
|
|
||||||
'finish_time' => now()
|
|
||||||
]);
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
exit
|
|
||||||
$importDatasource = \App\Models\ImportDatasource::create([
|
|
||||||
'message' => 'Testing BigdataResume Generation',
|
|
||||||
'status' => 'success',
|
|
||||||
'start_time' => now(),
|
|
||||||
'finish_time' => now()
|
|
||||||
]);
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
exit
|
|
||||||
$importDatasource = \App\Models\ImportDatasource::create([
|
|
||||||
'message' => 'Testing BigdataResume Generation',
|
|
||||||
'status' => 'success',
|
|
||||||
'start_time' => now(),
|
|
||||||
'finish_time' => now()
|
|
||||||
]);
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
exit
|
|
||||||
$importDatasource = \App\Models\ImportDatasource::create([
|
|
||||||
'message' => 'Testing BigdataResume Generation',
|
|
||||||
'status' => 'success',
|
|
||||||
'start_time' => now(),
|
|
||||||
'finish_time' => now()
|
|
||||||
]);
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
exit
|
|
||||||
$importDatasource = \App\Models\ImportDatasource::create([
|
|
||||||
'message' => 'Testing BigdataResume Generation',
|
|
||||||
'status' => 'success',
|
|
||||||
'start_time' => now(),
|
|
||||||
'finish_time' => now()
|
|
||||||
]);
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
exit
|
|
||||||
$importDatasource = \App\Models\ImportDatasource::create([
|
|
||||||
'message' => 'Testing BigdataResume Generation',
|
|
||||||
'status' => 'success',
|
|
||||||
'start_time' => now(),
|
|
||||||
'finish_time' => now()
|
|
||||||
]);
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
exit
|
|
||||||
$importDatasource = \App\Models\ImportDatasource::create([
|
|
||||||
'message' => 'Testing BigdataResume Generation',
|
|
||||||
'status' => 'success',
|
|
||||||
'start_time' => now(),
|
|
||||||
'finish_time' => now()
|
|
||||||
]);
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
exit
|
|
||||||
$importDatasource = \App\Models\ImportDatasource::create([
|
|
||||||
'message' => 'Testing BigdataResume Generation',
|
|
||||||
'status' => 'success',
|
|
||||||
'start_time' => now(),
|
|
||||||
'finish_time' => now()
|
|
||||||
]);
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
exit
|
|
||||||
$importDatasource = \App\Models\ImportDatasource::create([
|
|
||||||
'message' => 'Testing BigdataResume Generation',
|
|
||||||
'status' => 'success',
|
|
||||||
'start_time' => now(),
|
|
||||||
'finish_time' => now()
|
|
||||||
]);
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
exit
|
|
||||||
$importDatasource = \App\Models\ImportDatasource::create([
|
|
||||||
'message' => 'Testing BigdataResume Generation',
|
|
||||||
'status' => 'success',
|
|
||||||
'start_time' => now(),
|
|
||||||
'finish_time' => now()
|
|
||||||
]);
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
exit
|
|
||||||
$importDatasource = \App\Models\ImportDatasource::create([
|
|
||||||
'message' => 'Testing BigdataResume Generation',
|
|
||||||
'status' => 'success',
|
|
||||||
'start_time' => now(),
|
|
||||||
'finish_time' => now()
|
|
||||||
]);
|
|
||||||
$bigdataresume = BigdataResume::generateResumeData($importDatasource->id, 2025, 'simbg');
|
|
||||||
exit
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -21,3 +21,6 @@ yarn-error.log
|
|||||||
/.nova
|
/.nova
|
||||||
/.vscode
|
/.vscode
|
||||||
/.zed
|
/.zed
|
||||||
|
/.composer
|
||||||
|
/.config
|
||||||
|
/.npm
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
0 verbose cli /usr/bin/node /usr/bin/npm
|
|
||||||
1 info using npm@10.8.2
|
|
||||||
2 info using node@v18.20.8
|
|
||||||
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
|
|
||||||
4 silly config load:file:/var/www/.npmrc
|
|
||||||
5 silly config load:file:/usr/etc/npmrc
|
|
||||||
6 verbose title npm run build
|
|
||||||
7 verbose argv "run" "build"
|
|
||||||
8 verbose logfile logs-max:10 dir:/var/www/.npm/_logs/2025-08-15T03_44_29_538Z-
|
|
||||||
9 verbose logfile /var/www/.npm/_logs/2025-08-15T03_44_29_538Z-debug-0.log
|
|
||||||
10 silly logfile done cleaning log files
|
|
||||||
11 http fetch GET 200 https://registry.npmjs.org/npm 197ms
|
|
||||||
12 verbose cwd /var/www
|
|
||||||
13 verbose os Linux 6.6.87.2-microsoft-standard-WSL2
|
|
||||||
14 verbose node v18.20.8
|
|
||||||
15 verbose npm v10.8.2
|
|
||||||
16 notice
|
|
||||||
16 notice New [31mmajor[39m version of npm available! [31m10.8.2[39m -> [34m11.5.2[39m
|
|
||||||
16 notice Changelog: [34mhttps://github.com/npm/cli/releases/tag/v11.5.2[39m
|
|
||||||
16 notice To update run: [4mnpm install -g npm@11.5.2[24m
|
|
||||||
16 notice { force: true, [Symbol(proc-log.meta)]: true }
|
|
||||||
17 verbose exit 0
|
|
||||||
18 info ok
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
0 verbose cli /usr/bin/node /usr/bin/npm
|
|
||||||
1 info using npm@10.8.2
|
|
||||||
2 info using node@v18.20.8
|
|
||||||
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
|
|
||||||
4 silly config load:file:/var/www/.npmrc
|
|
||||||
5 silly config load:file:/usr/etc/npmrc
|
|
||||||
6 verbose title npm run build
|
|
||||||
7 verbose argv "run" "build"
|
|
||||||
8 verbose logfile logs-max:10 dir:/var/www/.npm/_logs/2025-08-19T05_29_08_290Z-
|
|
||||||
9 verbose logfile /var/www/.npm/_logs/2025-08-19T05_29_08_290Z-debug-0.log
|
|
||||||
10 silly logfile done cleaning log files
|
|
||||||
11 verbose cwd /var/www
|
|
||||||
12 verbose os Linux 6.6.87.2-microsoft-standard-WSL2
|
|
||||||
13 verbose node v18.20.8
|
|
||||||
14 verbose npm v10.8.2
|
|
||||||
15 verbose exit 0
|
|
||||||
16 info ok
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
0 verbose cli /usr/bin/node /usr/bin/npm
|
|
||||||
1 info using npm@10.8.2
|
|
||||||
2 info using node@v18.20.8
|
|
||||||
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
|
|
||||||
4 silly config load:file:/var/www/.npmrc
|
|
||||||
5 silly config load:file:/usr/etc/npmrc
|
|
||||||
6 verbose title npm run build
|
|
||||||
7 verbose argv "run" "build"
|
|
||||||
8 verbose logfile logs-max:10 dir:/var/www/.npm/_logs/2025-08-19T10_49_06_806Z-
|
|
||||||
9 verbose logfile /var/www/.npm/_logs/2025-08-19T10_49_06_806Z-debug-0.log
|
|
||||||
10 silly logfile done cleaning log files
|
|
||||||
11 verbose cwd /var/www
|
|
||||||
12 verbose os Linux 6.6.87.2-microsoft-standard-WSL2
|
|
||||||
13 verbose node v18.20.8
|
|
||||||
14 verbose npm v10.8.2
|
|
||||||
15 verbose exit 0
|
|
||||||
16 info ok
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
0 verbose cli /usr/bin/node /usr/bin/npm
|
|
||||||
1 info using npm@10.8.2
|
|
||||||
2 info using node@v18.20.8
|
|
||||||
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
|
|
||||||
4 silly config load:file:/var/www/.npmrc
|
|
||||||
5 silly config load:file:/usr/etc/npmrc
|
|
||||||
6 verbose title npm run build
|
|
||||||
7 verbose argv "run" "build"
|
|
||||||
8 verbose logfile logs-max:10 dir:/var/www/.npm/_logs/2025-08-19T16_34_13_318Z-
|
|
||||||
9 verbose logfile /var/www/.npm/_logs/2025-08-19T16_34_13_318Z-debug-0.log
|
|
||||||
10 silly logfile done cleaning log files
|
|
||||||
11 verbose cwd /var/www
|
|
||||||
12 verbose os Linux 6.6.87.2-microsoft-standard-WSL2
|
|
||||||
13 verbose node v18.20.8
|
|
||||||
14 verbose npm v10.8.2
|
|
||||||
15 verbose exit 0
|
|
||||||
16 info ok
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
0 verbose cli /usr/bin/node /usr/bin/npm
|
|
||||||
1 info using npm@10.8.2
|
|
||||||
2 info using node@v18.20.8
|
|
||||||
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
|
|
||||||
4 silly config load:file:/var/www/.npmrc
|
|
||||||
5 silly config load:file:/usr/etc/npmrc
|
|
||||||
6 verbose title npm run build
|
|
||||||
7 verbose argv "run" "build"
|
|
||||||
8 verbose logfile logs-max:10 dir:/var/www/.npm/_logs/2025-08-19T16_41_22_490Z-
|
|
||||||
9 verbose logfile /var/www/.npm/_logs/2025-08-19T16_41_22_490Z-debug-0.log
|
|
||||||
10 silly logfile done cleaning log files
|
|
||||||
11 verbose cwd /var/www
|
|
||||||
12 verbose os Linux 6.6.87.2-microsoft-standard-WSL2
|
|
||||||
13 verbose node v18.20.8
|
|
||||||
14 verbose npm v10.8.2
|
|
||||||
15 verbose exit 0
|
|
||||||
16 info ok
|
|
||||||
@@ -18,6 +18,9 @@ RUN apt-get update && apt-get install -y \
|
|||||||
git curl zip unzip libpng-dev libonig-dev libxml2-dev libzip-dev \
|
git curl zip unzip libpng-dev libonig-dev libxml2-dev libzip-dev \
|
||||||
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
|
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
|
||||||
|
|
||||||
|
# Override PHP memory limit
|
||||||
|
COPY docker/php/memory-limit.ini /usr/local/etc/php/conf.d/memory-limit.ini
|
||||||
|
|
||||||
# Install Node.js
|
# Install Node.js
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
&& apt-get install -y nodejs
|
&& apt-get install -y nodejs
|
||||||
@@ -64,6 +67,9 @@ RUN apt-get update && apt-get install -y \
|
|||||||
supervisor \
|
supervisor \
|
||||||
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
|
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
|
||||||
|
|
||||||
|
# Override PHP memory limit
|
||||||
|
COPY docker/php/memory-limit.ini /usr/local/etc/php/conf.d/memory-limit.ini
|
||||||
|
|
||||||
# Install Node.js
|
# Install Node.js
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
&& apt-get install -y nodejs
|
&& apt-get install -y nodejs
|
||||||
|
|||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,7 @@ class SyncPbgTaskPayments extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $description = 'Sync PBG task payments from Google Sheets REALISASI PAD';
|
protected $description = 'Sync PBG task payments from Google Sheets Sheet Data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
@@ -42,36 +42,13 @@ class SyncPbgTaskPayments extends Command
|
|||||||
$this->newLine();
|
$this->newLine();
|
||||||
|
|
||||||
$this->table(
|
$this->table(
|
||||||
['Metric', 'Count'],
|
['Metric', 'Value'],
|
||||||
[
|
[
|
||||||
['Total rows processed', $result['total_rows']],
|
['Inserted rows', $result['inserted'] ?? 0],
|
||||||
['Successful syncs', $result['successful_syncs']],
|
['Success', ($result['success'] ?? false) ? 'Yes' : 'No'],
|
||||||
['Failed syncs', $result['failed_syncs']],
|
|
||||||
['Tasks not found', $result['not_found_tasks']],
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Show success rate
|
|
||||||
$success_rate = $result['total_rows'] > 0
|
|
||||||
? round(($result['successful_syncs'] / $result['total_rows']) * 100, 2)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
$this->info("📈 Success rate: {$success_rate}%");
|
|
||||||
|
|
||||||
// Show errors if any
|
|
||||||
if (!empty($result['errors'])) {
|
|
||||||
$this->newLine();
|
|
||||||
$this->warn('⚠️ Errors encountered:');
|
|
||||||
foreach (array_slice($result['errors'], 0, 5) as $error) {
|
|
||||||
$this->line(" Row {$error['row']} ({$error['registration_number']}): {$error['error']}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count($result['errors']) > 5) {
|
|
||||||
$remaining = count($result['errors']) - 5;
|
|
||||||
$this->line(" ... and {$remaining} more errors (check logs for details)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->newLine();
|
$this->newLine();
|
||||||
$this->info('📝 Check Laravel logs for detailed information.');
|
$this->info('📝 Check Laravel logs for detailed information.');
|
||||||
|
|
||||||
|
|||||||
118
app/Exports/PbgTaskExport.php
Normal file
118
app/Exports/PbgTaskExport.php
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exports;
|
||||||
|
|
||||||
|
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||||
|
use App\Models\PbgTask;
|
||||||
|
use App\Enums\PbgTaskFilterData;
|
||||||
|
|
||||||
|
class PbgTaskExport implements FromCollection, WithHeadings
|
||||||
|
{
|
||||||
|
protected $category;
|
||||||
|
protected $year;
|
||||||
|
|
||||||
|
public function __construct(string $category, int $year)
|
||||||
|
{
|
||||||
|
$this->category = $category;
|
||||||
|
$this->year = $year;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Support\Collection
|
||||||
|
*/
|
||||||
|
public function collection()
|
||||||
|
{
|
||||||
|
$query = PbgTask::query()
|
||||||
|
->whereYear('task_created_at', $this->year);
|
||||||
|
|
||||||
|
// Menggunakan switch case karena lebih readable dan maintainable
|
||||||
|
// untuk multiple conditions yang berbeda
|
||||||
|
switch ($this->category) {
|
||||||
|
case PbgTaskFilterData::all->value:
|
||||||
|
// Tidak ada filter tambahan, ambil semua data
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::business->value:
|
||||||
|
$query->where('application_type', 'business');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::non_business->value:
|
||||||
|
$query->where('application_type', 'non-business');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::verified->value:
|
||||||
|
$query->where('is_valid', true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::non_verified->value:
|
||||||
|
$query->where('is_valid', false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::potention->value:
|
||||||
|
$query->where('status', 'potention');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::issuance_realization_pbg->value:
|
||||||
|
$query->where('status', 'issuance-realization-pbg');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::process_in_technical_office->value:
|
||||||
|
$query->where('status', 'process-in-technical-office');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::waiting_click_dpmptsp->value:
|
||||||
|
$query->where('status', 'waiting-click-dpmptsp');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::non_business_rab->value:
|
||||||
|
$query->where('application_type', 'non-business')
|
||||||
|
->where('consultation_type', 'rab');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::non_business_krk->value:
|
||||||
|
$query->where('application_type', 'non-business')
|
||||||
|
->where('consultation_type', 'krk');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::business_rab->value:
|
||||||
|
$query->where('application_type', 'business')
|
||||||
|
->where('consultation_type', 'rab');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::business_krk->value:
|
||||||
|
$query->where('application_type', 'business')
|
||||||
|
->where('consultation_type', 'krk');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PbgTaskFilterData::business_dlh->value:
|
||||||
|
$query->where('application_type', 'business')
|
||||||
|
->where('consultation_type', 'dlh');
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Jika category tidak dikenali, return empty collection
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->select([
|
||||||
|
'registration_number',
|
||||||
|
'document_number',
|
||||||
|
'owner_name',
|
||||||
|
'address',
|
||||||
|
'name as building_name',
|
||||||
|
'function_type'
|
||||||
|
])->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function headings(): array{
|
||||||
|
return [
|
||||||
|
'Nomor Registrasi',
|
||||||
|
'Nomor Dokumen',
|
||||||
|
'Nama Pemilik',
|
||||||
|
'Alamat Pemilik',
|
||||||
|
'Nama Bangunan',
|
||||||
|
'Fungsi Bangunan',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,15 +25,16 @@ class BigDataResumeController extends Controller
|
|||||||
{
|
{
|
||||||
try{
|
try{
|
||||||
$filterDate = $request->get("filterByDate");
|
$filterDate = $request->get("filterByDate");
|
||||||
$type = $request->get("type");
|
$type = trim($request->get("type"));
|
||||||
|
|
||||||
if (!$filterDate || $filterDate === "latest") {
|
if (!$filterDate || $filterDate === "latest") {
|
||||||
$big_data_resume = BigdataResume::latest()->first();
|
$big_data_resume = BigdataResume::where('resume_type', $type)->latest()->first();
|
||||||
if (!$big_data_resume) {
|
if (!$big_data_resume) {
|
||||||
return $this->response_empty_resume();
|
return $this->response_empty_resume();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$big_data_resume = BigdataResume::whereDate('created_at', $filterDate)
|
$big_data_resume = BigdataResume::whereDate('created_at', $filterDate)
|
||||||
|
->where('resume_type', $type)
|
||||||
->orderBy('id', 'desc')
|
->orderBy('id', 'desc')
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
@@ -75,19 +76,19 @@ class BigDataResumeController extends Controller
|
|||||||
$total_potensi_percentage = $big_data_resume->potention_sum > 0 && $target_pad > 0
|
$total_potensi_percentage = $big_data_resume->potention_sum > 0 && $target_pad > 0
|
||||||
? round(($big_data_resume->potention_sum / $target_pad) * 100, 2) : 0;
|
? round(($big_data_resume->potention_sum / $target_pad) * 100, 2) : 0;
|
||||||
|
|
||||||
// percentage verified document (verified_sum / potention_sum) - by value/amount
|
// // percentage verified document (verified_sum / potention_sum) - by value/amount
|
||||||
$verified_percentage = $big_data_resume->potention_sum > 0 && $big_data_resume->verified_sum >= 0
|
// $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;
|
// ? 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
|
// // 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
|
// $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;
|
// ? round(($big_data_resume->non_verified_sum / $big_data_resume->potention_sum) * 100, 2) : 0;
|
||||||
|
|
||||||
// Alternative: percentage by count (if needed)
|
// Alternative: percentage by count (if needed)
|
||||||
// $verified_count_percentage = $big_data_resume->potention_count > 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;
|
? round(($big_data_resume->verified_count / $big_data_resume->potention_count) * 100, 2) : 0;
|
||||||
// $non_verified_count_percentage = $big_data_resume->potention_count > 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;
|
? round(($big_data_resume->non_verified_count / $big_data_resume->potention_count) * 100, 2) : 0;
|
||||||
|
|
||||||
// percentage business document (business / non_verified)
|
// percentage business document (business / non_verified)
|
||||||
$business_percentage = $big_data_resume->non_verified_sum > 0 && $big_data_resume->business_sum >= 0
|
$business_percentage = $big_data_resume->non_verified_sum > 0 && $big_data_resume->business_sum >= 0
|
||||||
@@ -145,12 +146,12 @@ class BigDataResumeController extends Controller
|
|||||||
'verified_document' => [
|
'verified_document' => [
|
||||||
'sum' => (float) $big_data_resume->verified_sum,
|
'sum' => (float) $big_data_resume->verified_sum,
|
||||||
'count' => $big_data_resume->verified_count,
|
'count' => $big_data_resume->verified_count,
|
||||||
'percentage' => $verified_percentage
|
'percentage' => $verified_count_percentage
|
||||||
],
|
],
|
||||||
'non_verified_document' => [
|
'non_verified_document' => [
|
||||||
'sum' => (float) $big_data_resume->non_verified_sum,
|
'sum' => (float) $big_data_resume->non_verified_sum,
|
||||||
'count' => $big_data_resume->non_verified_count,
|
'count' => $big_data_resume->non_verified_count,
|
||||||
'percentage' => $non_verified_percentage
|
'percentage' => $non_verified_count_percentage
|
||||||
],
|
],
|
||||||
'business_document' => [
|
'business_document' => [
|
||||||
'sum' => (float) $big_data_resume->business_sum,
|
'sum' => (float) $big_data_resume->business_sum,
|
||||||
@@ -485,14 +486,14 @@ class BigDataResumeController extends Controller
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Get sum and count from PbgTaskPayment model
|
// Get sum and count from PbgTaskPayment model
|
||||||
$totalSum = PbgTaskPayment::whereYear('payment_date', date('Y'))->sum('pad_amount') ?? 0;
|
$stats = PbgTaskPayment::whereNotNull('payment_date_raw')
|
||||||
$totalCount = PbgTaskPayment::whereYear('payment_date', date('Y'))->count() ?? 0;
|
->whereNotNull('retribution_total_pad')
|
||||||
|
->whereYear('payment_date_raw', date('Y'))
|
||||||
|
->selectRaw('SUM(retribution_total_pad) as total_sum, COUNT(*) as total_count')
|
||||||
|
->first();
|
||||||
|
|
||||||
Log::info("Real-time PBG Task Payments Data", [
|
$totalSum = $stats->total_sum ?? 0;
|
||||||
'total_records' => $totalCount,
|
$totalCount = $stats->total_count ?? 0;
|
||||||
'total_sum' => $totalSum,
|
|
||||||
'source' => 'pbg_task_payments table'
|
|
||||||
]);
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'sum' => (float) $totalSum,
|
'sum' => (float) $totalSum,
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ class LackOfPotentialController extends Controller
|
|||||||
$total_tata_ruang_usaha = SpatialPlanning::where('building_function','like', '%usaha%')->count();
|
$total_tata_ruang_usaha = SpatialPlanning::where('building_function','like', '%usaha%')->count();
|
||||||
$total_tata_ruang_non_usaha = SpatialPlanning::where('building_function','not like', '%usaha%')->count();
|
$total_tata_ruang_non_usaha = SpatialPlanning::where('building_function','not like', '%usaha%')->count();
|
||||||
$data_report_tourism = TourismBasedKBLI::all();
|
$data_report_tourism = TourismBasedKBLI::all();
|
||||||
$data_pajak_reklame = Tax::where('tax_code','Reklame')->count();
|
$data_pajak_reklame = Tax::where('tax_code','Reklame')->distinct('business_name')->count();
|
||||||
$data_pajak_restoran = Tax::where('tax_code','Restoran')->count();
|
$data_pajak_restoran = Tax::where('tax_code','Restoran')->distinct('business_name')->count();
|
||||||
$data_pajak_hiburan = Tax::where('tax_code','Hiburan')->count();
|
$data_pajak_hiburan = Tax::where('tax_code','Hiburan')->distinct('business_name')->count();
|
||||||
$data_pajak_hotel = Tax::where('tax_code','Hotel')->count();
|
$data_pajak_hotel = Tax::where('tax_code','Hotel')->distinct('business_name')->count();
|
||||||
$data_pajak_parkir = Tax::where('tax_code','Parkir')->count();
|
$data_pajak_parkir = Tax::where('tax_code','Parkir')->distinct('business_name')->count();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'total_reklame' => $total_reklame,
|
'total_reklame' => $total_reklame,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use App\Models\DataSetting;
|
|||||||
use App\Models\ImportDatasource;
|
use App\Models\ImportDatasource;
|
||||||
use App\Models\PbgTask;
|
use App\Models\PbgTask;
|
||||||
use App\Models\PbgTaskGoogleSheet;
|
use App\Models\PbgTaskGoogleSheet;
|
||||||
use App\Services\GoogleSheetService;
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
@@ -20,10 +19,6 @@ use Illuminate\Validation\Rules\Enum;
|
|||||||
|
|
||||||
class PbgTaskController extends Controller
|
class PbgTaskController extends Controller
|
||||||
{
|
{
|
||||||
protected $googleSheetService;
|
|
||||||
public function __construct(GoogleSheetService $googleSheetService){
|
|
||||||
$this->googleSheetService = $googleSheetService;
|
|
||||||
}
|
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
info($request);
|
info($request);
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ class RequestAssignmentController extends Controller
|
|||||||
'attachments' => function ($q) {
|
'attachments' => function ($q) {
|
||||||
$q->whereIn('pbg_type', ['berita_acara', 'bukti_bayar']);
|
$q->whereIn('pbg_type', ['berita_acara', 'bukti_bayar']);
|
||||||
},
|
},
|
||||||
'pbg_task_retributions'
|
'pbg_task_retributions',
|
||||||
|
'pbg_task_detail',
|
||||||
|
'pbg_status'
|
||||||
])->orderBy('id', 'desc');
|
])->orderBy('id', 'desc');
|
||||||
|
|
||||||
// Log final query count for debugging
|
// Log final query count for debugging
|
||||||
@@ -126,10 +128,10 @@ class RequestAssignmentController extends Controller
|
|||||||
->whereIn("status", PbgTaskStatus::getNonVerified())
|
->whereIn("status", PbgTaskStatus::getNonVerified())
|
||||||
// Additional condition: unit IS NULL OR unit <= 1
|
// Additional condition: unit IS NULL OR unit <= 1
|
||||||
->where(function ($q3) {
|
->where(function ($q3) {
|
||||||
$q3->whereDoesntHave('pbg_task_details', function ($q4) {
|
$q3->whereDoesntHave('pbg_task_detail', function ($q4) {
|
||||||
$q4->where('unit', '>', 1);
|
$q4->where('unit', '>', 1);
|
||||||
})
|
})
|
||||||
->orWhereDoesntHave('pbg_task_details');
|
->orWhereDoesntHave('pbg_task_detail');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -152,7 +154,7 @@ class RequestAssignmentController extends Controller
|
|||||||
})
|
})
|
||||||
->orWhereNull('function_type');
|
->orWhereNull('function_type');
|
||||||
})
|
})
|
||||||
->whereHas('pbg_task_details', function ($q4) {
|
->whereHas('pbg_task_detail', function ($q4) {
|
||||||
$q4->where('unit', '>', 1);
|
$q4->where('unit', '>', 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -204,10 +206,10 @@ class RequestAssignmentController extends Controller
|
|||||||
->whereIn("status", PbgTaskStatus::getNonVerified())
|
->whereIn("status", PbgTaskStatus::getNonVerified())
|
||||||
// Additional condition: unit IS NULL OR unit <= 1
|
// Additional condition: unit IS NULL OR unit <= 1
|
||||||
->where(function ($q3) {
|
->where(function ($q3) {
|
||||||
$q3->whereDoesntHave('pbg_task_details', function ($q4) {
|
$q3->whereDoesntHave('pbg_task_detail', function ($q4) {
|
||||||
$q4->where('unit', '>', 1);
|
$q4->where('unit', '>', 1);
|
||||||
})
|
})
|
||||||
->orWhereDoesntHave('pbg_task_details');
|
->orWhereDoesntHave('pbg_task_detail');
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
->whereExists(function ($query) {
|
->whereExists(function ($query) {
|
||||||
@@ -232,10 +234,10 @@ class RequestAssignmentController extends Controller
|
|||||||
->whereIn("status", PbgTaskStatus::getNonVerified())
|
->whereIn("status", PbgTaskStatus::getNonVerified())
|
||||||
// Additional condition: unit IS NULL OR unit <= 1
|
// Additional condition: unit IS NULL OR unit <= 1
|
||||||
->where(function ($q3) {
|
->where(function ($q3) {
|
||||||
$q3->whereDoesntHave('pbg_task_details', function ($q4) {
|
$q3->whereDoesntHave('pbg_task_detail', function ($q4) {
|
||||||
$q4->where('unit', '>', 1);
|
$q4->where('unit', '>', 1);
|
||||||
})
|
})
|
||||||
->orWhereDoesntHave('pbg_task_details');
|
->orWhereDoesntHave('pbg_task_detail');
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
->whereExists(function ($query) {
|
->whereExists(function ($query) {
|
||||||
@@ -265,7 +267,7 @@ class RequestAssignmentController extends Controller
|
|||||||
})
|
})
|
||||||
->orWhereNull('function_type');
|
->orWhereNull('function_type');
|
||||||
})
|
})
|
||||||
->whereHas('pbg_task_details', function ($q4) {
|
->whereHas('pbg_task_detail', function ($q4) {
|
||||||
$q4->where('unit', '>', 1);
|
$q4->where('unit', '>', 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -299,7 +301,7 @@ class RequestAssignmentController extends Controller
|
|||||||
})
|
})
|
||||||
->orWhereNull('function_type');
|
->orWhereNull('function_type');
|
||||||
})
|
})
|
||||||
->whereHas('pbg_task_details', function ($q4) {
|
->whereHas('pbg_task_detail', function ($q4) {
|
||||||
$q4->where('unit', '>', 1);
|
$q4->where('unit', '>', 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -333,7 +335,7 @@ class RequestAssignmentController extends Controller
|
|||||||
})
|
})
|
||||||
->orWhereNull('function_type');
|
->orWhereNull('function_type');
|
||||||
})
|
})
|
||||||
->whereHas('pbg_task_details', function ($q4) {
|
->whereHas('pbg_task_detail', function ($q4) {
|
||||||
$q4->where('unit', '>', 1);
|
$q4->where('unit', '>', 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -528,7 +530,7 @@ class RequestAssignmentController extends Controller
|
|||||||
})
|
})
|
||||||
->orWhereNull('function_type');
|
->orWhereNull('function_type');
|
||||||
})
|
})
|
||||||
->whereHas('pbg_task_details', function ($q4) {
|
->whereHas('pbg_task_detail', function ($q4) {
|
||||||
$q4->where('unit', '>', 1);
|
$q4->where('unit', '>', 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -552,10 +554,10 @@ class RequestAssignmentController extends Controller
|
|||||||
->whereIn("status", PbgTaskStatus::getNonVerified())
|
->whereIn("status", PbgTaskStatus::getNonVerified())
|
||||||
// Additional condition: unit IS NULL OR unit <= 1
|
// Additional condition: unit IS NULL OR unit <= 1
|
||||||
->where(function ($q3) {
|
->where(function ($q3) {
|
||||||
$q3->whereDoesntHave('pbg_task_details', function ($q4) {
|
$q3->whereDoesntHave('pbg_task_detail', function ($q4) {
|
||||||
$q4->where('unit', '>', 1);
|
$q4->where('unit', '>', 1);
|
||||||
})
|
})
|
||||||
->orWhereDoesntHave('pbg_task_details');
|
->orWhereDoesntHave('pbg_task_detail');
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
->where('is_valid', true)
|
->where('is_valid', true)
|
||||||
|
|||||||
@@ -54,7 +54,10 @@ class AuthenticatedSessionController extends Controller
|
|||||||
session(['login_timestamp' => now()->timestamp]);
|
session(['login_timestamp' => now()->timestamp]);
|
||||||
session(['user_id' => $user->id]);
|
session(['user_id' => $user->id]);
|
||||||
|
|
||||||
return redirect()->intended(RouteServiceProvider::HOME);
|
// Append menu_id dynamically to HOME
|
||||||
|
$menuId = optional(\App\Models\Menu::where('name', 'Dashboard Pimpinan SIMBG')->first())->id;
|
||||||
|
$home = RouteServiceProvider::HOME . ($menuId ? ('?menu_id=' . $menuId) : '');
|
||||||
|
return redirect()->intended($home);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use App\Models\PbgTask;
|
|||||||
use App\Models\TaskAssignment;
|
use App\Models\TaskAssignment;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class QuickSearchController extends Controller
|
class QuickSearchController extends Controller
|
||||||
{
|
{
|
||||||
@@ -16,6 +17,10 @@ class QuickSearchController extends Controller
|
|||||||
return view("quick-search.index");
|
return view("quick-search.index");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function public_search(){
|
||||||
|
return view("public-search.index");
|
||||||
|
}
|
||||||
|
|
||||||
public function search_result(Request $request){
|
public function search_result(Request $request){
|
||||||
$keyword = $request->get("keyword");
|
$keyword = $request->get("keyword");
|
||||||
|
|
||||||
@@ -25,9 +30,14 @@ class QuickSearchController extends Controller
|
|||||||
public function quick_search_datatable(Request $request)
|
public function quick_search_datatable(Request $request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$query = PbgTask::leftJoin('pbg_task_details', 'pbg_task.uuid', '=', 'pbg_task_details.pbg_task_uid')
|
// Gunakan subquery untuk performa yang lebih baik dan menghindari duplikasi
|
||||||
->select('pbg_task.*')
|
$query = PbgTask::select([
|
||||||
->orderBy('pbg_task.id', 'desc');
|
'pbg_task.*',
|
||||||
|
DB::raw('(SELECT name_building FROM pbg_task_details WHERE pbg_task_details.pbg_task_uid = pbg_task.uuid LIMIT 1) as name_building'),
|
||||||
|
DB::raw('(SELECT nilai_retribusi_bangunan FROM pbg_task_retributions WHERE pbg_task_retributions.pbg_task_uid = pbg_task.uuid LIMIT 1) as nilai_retribusi_bangunan'),
|
||||||
|
DB::raw('(SELECT note FROM pbg_statuses WHERE pbg_statuses.pbg_task_uuid = pbg_task.uuid LIMIT 1) as note')
|
||||||
|
])
|
||||||
|
->orderBy('pbg_task.id', 'desc');
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$search = trim($request->get('search'));
|
$search = trim($request->get('search'));
|
||||||
@@ -36,7 +46,12 @@ class QuickSearchController extends Controller
|
|||||||
->orWhere('pbg_task.name', 'LIKE', "%$search%")
|
->orWhere('pbg_task.name', 'LIKE', "%$search%")
|
||||||
->orWhere('pbg_task.owner_name', 'LIKE', "%$search%")
|
->orWhere('pbg_task.owner_name', 'LIKE', "%$search%")
|
||||||
->orWhere('pbg_task.address', 'LIKE', "%$search%")
|
->orWhere('pbg_task.address', 'LIKE', "%$search%")
|
||||||
->orWhere('pbg_task_details.name_building', 'LIKE', "%$search%");
|
->orWhereExists(function ($subQuery) use ($search) {
|
||||||
|
$subQuery->select(DB::raw(1))
|
||||||
|
->from('pbg_task_details')
|
||||||
|
->whereColumn('pbg_task_details.pbg_task_uid', 'pbg_task.uuid')
|
||||||
|
->where('pbg_task_details.name_building', 'LIKE', "%$search%");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,13 +65,85 @@ class QuickSearchController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function public_search_datatable(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Hanya proses jika ada keyword search
|
||||||
|
if (!$request->filled('search') || trim($request->get('search')) === '') {
|
||||||
|
return response()->json([
|
||||||
|
'data' => [],
|
||||||
|
'total' => 0,
|
||||||
|
'current_page' => 1,
|
||||||
|
'last_page' => 1,
|
||||||
|
'per_page' => 15,
|
||||||
|
'from' => null,
|
||||||
|
'to' => null
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$search = trim($request->get('search'));
|
||||||
|
|
||||||
|
// Validasi minimal 3 karakter
|
||||||
|
if (strlen($search) < 3) {
|
||||||
|
return response()->json([
|
||||||
|
'data' => [],
|
||||||
|
'total' => 0,
|
||||||
|
'current_page' => 1,
|
||||||
|
'last_page' => 1,
|
||||||
|
'per_page' => 15,
|
||||||
|
'from' => null,
|
||||||
|
'to' => null,
|
||||||
|
'message' => 'Minimal 3 karakter untuk pencarian'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gunakan subquery untuk performa yang lebih baik dan menghindari duplikasi
|
||||||
|
$query = PbgTask::select([
|
||||||
|
'pbg_task.*',
|
||||||
|
DB::raw('(SELECT name_building FROM pbg_task_details WHERE pbg_task_details.pbg_task_uid = pbg_task.uuid LIMIT 1) as name_building'),
|
||||||
|
DB::raw('(SELECT nilai_retribusi_bangunan FROM pbg_task_retributions WHERE pbg_task_retributions.pbg_task_uid = pbg_task.uuid LIMIT 1) as nilai_retribusi_bangunan'),
|
||||||
|
DB::raw('(SELECT note FROM pbg_statuses WHERE pbg_statuses.pbg_task_uuid = pbg_task.uuid LIMIT 1) as note')
|
||||||
|
])
|
||||||
|
->where(function ($q) use ($search) {
|
||||||
|
$q->where('pbg_task.registration_number', 'LIKE', "%$search%")
|
||||||
|
->orWhere('pbg_task.name', 'LIKE', "%$search%")
|
||||||
|
->orWhere('pbg_task.owner_name', 'LIKE', "%$search%")
|
||||||
|
->orWhere('pbg_task.address', 'LIKE', "%$search%")
|
||||||
|
->orWhereExists(function ($subQuery) use ($search) {
|
||||||
|
$subQuery->select(DB::raw(1))
|
||||||
|
->from('pbg_task_details')
|
||||||
|
->whereColumn('pbg_task_details.pbg_task_uid', 'pbg_task.uuid')
|
||||||
|
->where('pbg_task_details.name_building', 'LIKE', "%$search%");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->orderBy('pbg_task.id', 'desc');
|
||||||
|
|
||||||
|
$result = $query->paginate();
|
||||||
|
|
||||||
|
// Tambahkan message jika tidak ada hasil
|
||||||
|
if ($result->total() === 0) {
|
||||||
|
$result = $result->toArray();
|
||||||
|
$result['message'] = 'Tidak ada data yang ditemukan';
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($result);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error("Error fetching datatable data: " . $e->getMessage());
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Terjadi kesalahan saat mengambil data.',
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function show($id)
|
public function show($id)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$data = PbgTask::with([
|
$data = PbgTask::with([
|
||||||
'pbg_task_retributions',
|
'pbg_task_retributions',
|
||||||
'pbg_task_index_integrations',
|
'pbg_task_index_integrations',
|
||||||
'pbg_task_retributions.pbg_task_prasarana'
|
'pbg_task_retributions.pbg_task_prasarana',
|
||||||
|
'pbg_status'
|
||||||
])->findOrFail($id);
|
])->findOrFail($id);
|
||||||
|
|
||||||
$statusOptions = PbgTaskStatus::getStatuses();
|
$statusOptions = PbgTaskStatus::getStatuses();
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class PbgTaskController extends Controller
|
|||||||
'destroyer' => $destroyer,
|
'destroyer' => $destroyer,
|
||||||
'filter' => $filter,
|
'filter' => $filter,
|
||||||
'filterOptions' => PbgTaskFilterData::getAllOptions(),
|
'filterOptions' => PbgTaskFilterData::getAllOptions(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,6 +61,7 @@ class PbgTaskController extends Controller
|
|||||||
'pbg_task_index_integrations',
|
'pbg_task_index_integrations',
|
||||||
'pbg_task_retributions.pbg_task_prasarana',
|
'pbg_task_retributions.pbg_task_prasarana',
|
||||||
'pbg_task_detail',
|
'pbg_task_detail',
|
||||||
|
'pbg_status',
|
||||||
'dataLists' => function($query) {
|
'dataLists' => function($query) {
|
||||||
$query->orderBy('data_type')->orderBy('name');
|
$query->orderBy('data_type')->orderBy('name');
|
||||||
}
|
}
|
||||||
@@ -69,20 +70,6 @@ class PbgTaskController extends Controller
|
|||||||
// Group data lists by data_type for easier display
|
// Group data lists by data_type for easier display
|
||||||
$dataListsByType = $data->dataLists->groupBy('data_type');
|
$dataListsByType = $data->dataLists->groupBy('data_type');
|
||||||
|
|
||||||
// Debug: Log the data types found for this task
|
|
||||||
\Log::info('PBG Task Data Lists', [
|
|
||||||
'task_uuid' => $data->uuid,
|
|
||||||
'total_data_lists' => $data->dataLists->count(),
|
|
||||||
'data_types_found' => $dataListsByType->keys()->toArray(),
|
|
||||||
'data_types_with_names' => $dataListsByType->map(function($items, $type) {
|
|
||||||
return [
|
|
||||||
'type' => $type,
|
|
||||||
'name' => $items->first()->data_type_name ?? "Type {$type}",
|
|
||||||
'count' => $items->count()
|
|
||||||
];
|
|
||||||
})->values()->toArray()
|
|
||||||
]);
|
|
||||||
|
|
||||||
$statusOptions = PbgTaskStatus::getStatuses();
|
$statusOptions = PbgTaskStatus::getStatuses();
|
||||||
$applicationTypes = PbgTaskApplicationTypes::labels();
|
$applicationTypes = PbgTaskApplicationTypes::labels();
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,12 @@
|
|||||||
namespace App\Http\Controllers\Settings;
|
namespace App\Http\Controllers\Settings;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Services\ServiceSIMBG;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
class SyncronizeController extends Controller
|
class SyncronizeController extends Controller
|
||||||
{
|
{
|
||||||
protected $service_simbg;
|
|
||||||
public function __construct(ServiceSIMBG $service_simbg){
|
|
||||||
$this->service_simbg = $service_simbg;
|
|
||||||
}
|
|
||||||
public function index(Request $request){
|
public function index(Request $request){
|
||||||
$menuId = $request->query('menu_id');
|
$menuId = $request->query('menu_id');
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
@@ -37,36 +32,4 @@ class SyncronizeController extends Controller
|
|||||||
|
|
||||||
return view('settings.syncronize.index', compact('creator', 'updater', 'destroyer'));
|
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,
|
'url' => $this->url,
|
||||||
'sort_order' => $this->sort_order,
|
'sort_order' => $this->sort_order,
|
||||||
'parent' => $this->parent ? new MenuResource($this->parent) : null,
|
'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,
|
'created_at' => $this->created_at,
|
||||||
'updated_at' => $this->updated_at
|
'updated_at' => $this->updated_at
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ class RequestAssignmentResouce extends JsonResource
|
|||||||
->sortByDesc('created_at')
|
->sortByDesc('created_at')
|
||||||
->first(),
|
->first(),
|
||||||
'pbg_task_retributions' => $this->pbg_task_retributions,
|
'pbg_task_retributions' => $this->pbg_task_retributions,
|
||||||
|
'pbg_task_detail' => $this->pbg_task_detail,
|
||||||
|
'pbg_status' => $this->pbg_status,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,28 +59,12 @@ class ScrapingDataJob implements ShouldQueue
|
|||||||
'start_time' => now(),
|
'start_time' => now(),
|
||||||
'failed_uuid' => null
|
'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...']);
|
$import_datasource->update(['message' => 'Scraping PBG Task parent data...']);
|
||||||
|
|
||||||
$service_pbg_task->run_service();
|
$service_pbg_task->run_service();
|
||||||
Log::info("PBG Task parent data scraping completed");
|
|
||||||
|
|
||||||
// STEP 3: Get all PBG tasks for detail scraping
|
// STEP 3: Get all PBG tasks for detail scraping
|
||||||
$totalTasks = PbgTask::count();
|
$totalTasks = PbgTask::count();
|
||||||
Log::info("=== STEP 3: SCRAPING PBG TASK DETAILS ===", [
|
|
||||||
'total_tasks' => $totalTasks
|
|
||||||
]);
|
|
||||||
|
|
||||||
$import_datasource->update([
|
$import_datasource->update([
|
||||||
'message' => "Scraping details for {$totalTasks} PBG tasks..."
|
'message' => "Scraping details for {$totalTasks} PBG tasks..."
|
||||||
@@ -135,13 +119,10 @@ class ScrapingDataJob implements ShouldQueue
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Log::info("Task details scraping completed", [
|
$import_datasource->update(['message' => 'Scraping Google Sheet data...']);
|
||||||
'processed_tasks' => $processedTasks,
|
|
||||||
'total_tasks' => $totalTasks
|
$service_google_sheet->run_service();
|
||||||
]);
|
|
||||||
|
|
||||||
// STEP 4: Generate BigData Resume
|
|
||||||
Log::info("=== STEP 4: GENERATING BIGDATA RESUME ===");
|
|
||||||
$import_datasource->update(['message' => 'Generating BigData resume...']);
|
$import_datasource->update(['message' => 'Generating BigData resume...']);
|
||||||
|
|
||||||
BigdataResume::generateResumeData($import_datasource->id, date('Y'), "simbg");
|
BigdataResume::generateResumeData($import_datasource->id, date('Y'), "simbg");
|
||||||
@@ -200,6 +181,7 @@ class ScrapingDataJob implements ShouldQueue
|
|||||||
$service->scraping_pbg_data_list($uuid);
|
$service->scraping_pbg_data_list($uuid);
|
||||||
$service->scraping_task_retributions($uuid);
|
$service->scraping_task_retributions($uuid);
|
||||||
$service->scraping_task_integrations($uuid);
|
$service->scraping_task_integrations($uuid);
|
||||||
|
$service->scraping_task_detail_status($uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
57
app/Models/PbgStatus.php
Normal file
57
app/Models/PbgStatus.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
class PbgStatus extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'pbg_statuses';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'pbg_task_uuid',
|
||||||
|
'status',
|
||||||
|
'status_name',
|
||||||
|
'slf_status',
|
||||||
|
'slf_status_name',
|
||||||
|
'due_date',
|
||||||
|
'uid',
|
||||||
|
'note',
|
||||||
|
'file',
|
||||||
|
'data_due_date',
|
||||||
|
'data_created_at',
|
||||||
|
'slf_data',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function pbgTask()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(PbgTask::class, 'pbg_task_uuid', 'uuid');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createOrUpdateFromApi(array $apiResponse, string $pbgTaskUuid)
|
||||||
|
{
|
||||||
|
$data = $apiResponse['data'] ?? [];
|
||||||
|
|
||||||
|
return self::updateOrCreate(
|
||||||
|
[
|
||||||
|
'pbg_task_uuid' => $pbgTaskUuid,
|
||||||
|
'status' => $apiResponse['status'], // key pencarian unik
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'status_name' => $apiResponse['status_name'] ?? null,
|
||||||
|
'slf_status' => $apiResponse['slf_status'] ?? null,
|
||||||
|
'slf_status_name' => $apiResponse['slf_status_name'] ?? null,
|
||||||
|
'due_date' => $apiResponse['due_date'] ?? null,
|
||||||
|
|
||||||
|
// nested data
|
||||||
|
'uid' => $data['uid'] ?? null,
|
||||||
|
'note' => $data['note'] ?? null,
|
||||||
|
'file' => $data['file'] ?? null,
|
||||||
|
'data_due_date' => $data['due_date'] ?? null,
|
||||||
|
'data_created_at' => isset($data['created_at']) ? Carbon::parse($data['created_at'])->format('Y-m-d H:i:s') : null,
|
||||||
|
|
||||||
|
'slf_data' => $apiResponse['slf_data'] ?? null,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,6 +65,11 @@ class PbgTask extends Model
|
|||||||
return $this->hasMany(PbgTaskDetailDataList::class, 'pbg_task_uuid', 'uuid');
|
return $this->hasMany(PbgTaskDetailDataList::class, 'pbg_task_uuid', 'uuid');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function pbg_status()
|
||||||
|
{
|
||||||
|
return $this->hasOne(PbgStatus::class, 'pbg_task_uuid', 'uuid');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get only data lists with files
|
* Get only data lists with files
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,15 +11,79 @@ class PbgTaskPayment extends Model
|
|||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'pbg_task_id',
|
'pbg_task_id',
|
||||||
'pbg_task_uid',
|
'pbg_task_uid',
|
||||||
'registration_number',
|
// mapped fields
|
||||||
'sts_form_number',
|
'row_no',
|
||||||
'payment_date',
|
'consultation_type',
|
||||||
'pad_amount'
|
'source_registration_number',
|
||||||
|
'owner_name',
|
||||||
|
'building_location',
|
||||||
|
'building_function',
|
||||||
|
'building_name',
|
||||||
|
'application_date_raw',
|
||||||
|
'verification_status',
|
||||||
|
'application_status',
|
||||||
|
'owner_address',
|
||||||
|
'owner_phone',
|
||||||
|
'owner_email',
|
||||||
|
'note_date_raw',
|
||||||
|
'document_shortage_note',
|
||||||
|
'image_url',
|
||||||
|
'krk_kkpr',
|
||||||
|
'krk_number',
|
||||||
|
'lh',
|
||||||
|
'ska',
|
||||||
|
'remarks',
|
||||||
|
'helpdesk',
|
||||||
|
'person_in_charge',
|
||||||
|
'pbg_operator',
|
||||||
|
'ownership',
|
||||||
|
'taru_potential',
|
||||||
|
'agency_validation',
|
||||||
|
'retribution_category',
|
||||||
|
'ba_tpt_number',
|
||||||
|
'ba_tpt_date_raw',
|
||||||
|
'ba_tpa_number',
|
||||||
|
'ba_tpa_date_raw',
|
||||||
|
'skrd_number',
|
||||||
|
'skrd_date_raw',
|
||||||
|
'ptsp_status',
|
||||||
|
'issued_status',
|
||||||
|
'payment_date_raw',
|
||||||
|
'sts_format',
|
||||||
|
'issuance_year',
|
||||||
|
'current_year',
|
||||||
|
'village',
|
||||||
|
'district',
|
||||||
|
'building_area',
|
||||||
|
'building_height',
|
||||||
|
'floor_count',
|
||||||
|
'unit_count',
|
||||||
|
'proposed_retribution',
|
||||||
|
'retribution_total_simbg',
|
||||||
|
'retribution_total_pad',
|
||||||
|
'penalty_amount',
|
||||||
|
'business_category',
|
||||||
|
'created_at',
|
||||||
|
'updated_at'
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'payment_date' => 'date',
|
'application_date_raw' => 'date',
|
||||||
'pad_amount' => 'decimal:2'
|
'note_date_raw' => 'date',
|
||||||
|
'ba_tpt_date_raw' => 'date',
|
||||||
|
'ba_tpa_date_raw' => 'date',
|
||||||
|
'skrd_date_raw' => 'date',
|
||||||
|
'payment_date_raw' => 'date',
|
||||||
|
'issuance_year' => 'integer',
|
||||||
|
'current_year' => 'integer',
|
||||||
|
'floor_count' => 'integer',
|
||||||
|
'unit_count' => 'integer',
|
||||||
|
'building_area' => 'decimal:2',
|
||||||
|
'building_height' => 'decimal:2',
|
||||||
|
'proposed_retribution' => 'decimal:2',
|
||||||
|
'retribution_total_simbg' => 'decimal:2',
|
||||||
|
'retribution_total_pad' => 'decimal:2',
|
||||||
|
'penalty_amount' => 'decimal:2'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ use Illuminate\Support\Facades\Blade;
|
|||||||
use Illuminate\Support\Facades\View;
|
use Illuminate\Support\Facades\View;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use App\Services\ServiceSIMBG;
|
|
||||||
use App\Services\GoogleSheetService;
|
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -64,7 +62,8 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
$query->whereHas('roles', function ($subQuery) use ($user) {
|
$query->whereHas('roles', function ($subQuery) use ($user) {
|
||||||
$subQuery->whereIn('roles.id', $user->roles->pluck('id'))
|
$subQuery->whereIn('roles.id', $user->roles->pluck('id'))
|
||||||
->where('role_menu.allow_show', 1);
|
->where('role_menu.allow_show', 1);
|
||||||
});
|
})
|
||||||
|
->orderBy('sort_order', 'asc');
|
||||||
}])
|
}])
|
||||||
->whereNull('parent_id') // Ambil hanya menu utama
|
->whereNull('parent_id') // Ambil hanya menu utama
|
||||||
->orderBy('sort_order', 'asc')
|
->orderBy('sort_order', 'asc')
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvi
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\RateLimiter;
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use App\Models\Menu;
|
||||||
|
|
||||||
class RouteServiceProvider extends ServiceProvider
|
class RouteServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -17,7 +18,7 @@ class RouteServiceProvider extends ServiceProvider
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public const HOME = '/home';
|
public const HOME = '/dashboards/bigdata';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define your route model bindings, pattern filters, and other route configuration.
|
* Define your route model bindings, pattern filters, and other route configuration.
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,9 @@ use Exception;
|
|||||||
use Google\Client as Google_Client;
|
use Google\Client as Google_Client;
|
||||||
use Google\Service\Sheets as Google_Service_Sheets;
|
use Google\Service\Sheets as Google_Service_Sheets;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use App\Models\PbgTask;
|
||||||
class ServiceGoogleSheet
|
class ServiceGoogleSheet
|
||||||
{
|
{
|
||||||
protected $client;
|
protected $client;
|
||||||
@@ -36,8 +39,8 @@ class ServiceGoogleSheet
|
|||||||
|
|
||||||
public function run_service(){
|
public function run_service(){
|
||||||
try{
|
try{
|
||||||
// $this->sync_big_data();
|
|
||||||
$this->sync_google_sheet_data();
|
$this->sync_google_sheet_data();
|
||||||
|
$this->sync_pbg_task_payments();
|
||||||
}catch(Exception $e){
|
}catch(Exception $e){
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
@@ -248,7 +251,6 @@ class ServiceGoogleSheet
|
|||||||
]);
|
]);
|
||||||
try {
|
try {
|
||||||
$sections = [
|
$sections = [
|
||||||
'TARGET_PAD' => "TARGET PAD 2024",
|
|
||||||
'KEKURANGAN_POTENSI' => "DEVIASI TARGET DENGAN POTENSI TOTAL BERKAS",
|
'KEKURANGAN_POTENSI' => "DEVIASI TARGET DENGAN POTENSI TOTAL BERKAS",
|
||||||
'TOTAL_POTENSI_BERKAS' => "•TOTAL BERKAS 2025",
|
'TOTAL_POTENSI_BERKAS' => "•TOTAL BERKAS 2025",
|
||||||
'BELUM_TERVERIFIKASI' => "•BERKAS AKTUAL BELUM TERVERIFIKASI (POTENSI):",
|
'BELUM_TERVERIFIKASI' => "•BERKAS AKTUAL BELUM TERVERIFIKASI (POTENSI):",
|
||||||
@@ -314,7 +316,6 @@ class ServiceGoogleSheet
|
|||||||
|
|
||||||
// Save data settings
|
// Save data settings
|
||||||
$dataSettings = [
|
$dataSettings = [
|
||||||
'TARGET_PAD' => $result['TARGET_PAD']['nominal'] ?? null,
|
|
||||||
'KEKURANGAN_POTENSI' => $result['KEKURANGAN_POTENSI']['nominal'] ?? null,
|
'KEKURANGAN_POTENSI' => $result['KEKURANGAN_POTENSI']['nominal'] ?? null,
|
||||||
'REALISASI_TERBIT_PBG_COUNT' => $result['REALISASI_TERBIT_PBG']['total'] ?? null,
|
'REALISASI_TERBIT_PBG_COUNT' => $result['REALISASI_TERBIT_PBG']['total'] ?? null,
|
||||||
'REALISASI_TERBIT_PBG_SUM' => $result['REALISASI_TERBIT_PBG']['nominal'] ?? null,
|
'REALISASI_TERBIT_PBG_SUM' => $result['REALISASI_TERBIT_PBG']['nominal'] ?? null,
|
||||||
@@ -363,7 +364,6 @@ class ServiceGoogleSheet
|
|||||||
public function get_big_resume_data(){
|
public function get_big_resume_data(){
|
||||||
try {
|
try {
|
||||||
$sections = [
|
$sections = [
|
||||||
'TARGET_PAD' => "TARGET PAD 2024",
|
|
||||||
'KEKURANGAN_POTENSI' => "DEVIASI TARGET DENGAN POTENSI TOTAL BERKAS",
|
'KEKURANGAN_POTENSI' => "DEVIASI TARGET DENGAN POTENSI TOTAL BERKAS",
|
||||||
'TOTAL_POTENSI_BERKAS' => "•TOTAL BERKAS 2025",
|
'TOTAL_POTENSI_BERKAS' => "•TOTAL BERKAS 2025",
|
||||||
'BELUM_TERVERIFIKASI' => "•BERKAS AKTUAL BELUM TERVERIFIKASI (POTENSI):",
|
'BELUM_TERVERIFIKASI' => "•BERKAS AKTUAL BELUM TERVERIFIKASI (POTENSI):",
|
||||||
@@ -391,7 +391,6 @@ class ServiceGoogleSheet
|
|||||||
|
|
||||||
// Save data settings
|
// Save data settings
|
||||||
$dataSettings = [
|
$dataSettings = [
|
||||||
'TARGET_PAD' => $this->convertToDecimal($result['TARGET_PAD']['nominal']) ?? 0,
|
|
||||||
'KEKURANGAN_POTENSI' => $this->convertToDecimal($result['KEKURANGAN_POTENSI']['nominal']) ?? 0,
|
'KEKURANGAN_POTENSI' => $this->convertToDecimal($result['KEKURANGAN_POTENSI']['nominal']) ?? 0,
|
||||||
'REALISASI_TERBIT_PBG_COUNT' => $this->convertToInteger($result['REALISASI_TERBIT_PBG']['total']) ?? 0,
|
'REALISASI_TERBIT_PBG_COUNT' => $this->convertToInteger($result['REALISASI_TERBIT_PBG']['total']) ?? 0,
|
||||||
'REALISASI_TERBIT_PBG_SUM' => $this->convertToDecimal($result['REALISASI_TERBIT_PBG']['nominal']) ?? 0,
|
'REALISASI_TERBIT_PBG_SUM' => $this->convertToDecimal($result['REALISASI_TERBIT_PBG']['nominal']) ?? 0,
|
||||||
@@ -427,196 +426,427 @@ class ServiceGoogleSheet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_realisasi_pad_data(){
|
/**
|
||||||
|
* Get sheet data where the first row is treated as headers, and subsequent rows
|
||||||
|
* are returned as associative arrays keyed by header names. Supports selecting
|
||||||
|
* a contiguous column range plus additional specific columns.
|
||||||
|
*
|
||||||
|
* Example: get_sheet_data_with_headers_range('Data', 'A', 'AX', ['BX'])
|
||||||
|
*
|
||||||
|
* @param string $sheet_name
|
||||||
|
* @param string $start_column_letter Inclusive start column letter (e.g., 'A')
|
||||||
|
* @param string $end_column_letter Inclusive end column letter (e.g., 'AX')
|
||||||
|
* @param array $extra_column_letters Additional discrete column letters (e.g., ['BX'])
|
||||||
|
* @return array{headers: array<int,string>, data: array<int,array<string,?string>>, selected_columns: array<int,int>}
|
||||||
|
*/
|
||||||
|
public function get_sheet_data_with_headers_range(string $sheet_name, string $start_column_letter, string $end_column_letter, array $extra_column_letters = [])
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
// Get data from "REALISASI PAD" sheet
|
$sheet_data = $this->get_data_by_sheet_name($sheet_name);
|
||||||
$sheet_data = $this->get_data_by_sheet_name("REALISASI PAD");
|
|
||||||
|
|
||||||
if (empty($sheet_data)) {
|
if (empty($sheet_data)) {
|
||||||
Log::warning("No data found in REALISASI PAD sheet");
|
Log::warning("No data found in sheet", ['sheet_name' => $sheet_name]);
|
||||||
return [];
|
return [
|
||||||
|
'headers' => [],
|
||||||
|
'data' => [],
|
||||||
|
'selected_columns' => []
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Column indices: C=2, AK=36, AL=37, AW=48 (0-based)
|
// Build selected column indices: range A..AX and extras like BX
|
||||||
$columns = [
|
$selected_indices = $this->expandColumnRangeToIndices($start_column_letter, $end_column_letter);
|
||||||
'C' => 2,
|
foreach ($extra_column_letters as $letter) {
|
||||||
'AK' => 36,
|
$selected_indices[] = $this->columnLetterToIndex($letter);
|
||||||
'AL' => 37,
|
}
|
||||||
'AW' => 48
|
// Ensure unique and sorted
|
||||||
];
|
$selected_indices = array_values(array_unique($selected_indices));
|
||||||
|
sort($selected_indices);
|
||||||
|
|
||||||
$result = [
|
$result = [
|
||||||
'headers' => [],
|
'headers' => [],
|
||||||
'data' => []
|
'data' => [],
|
||||||
|
'selected_columns' => $selected_indices
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($sheet_data as $row_index => $row) {
|
foreach ($sheet_data as $row_index => $row) {
|
||||||
if (!is_array($row)) continue;
|
if (!is_array($row)) continue;
|
||||||
|
|
||||||
if ($row_index === 0) {
|
if ($row_index === 0) {
|
||||||
// First row contains headers
|
// First row contains headers (by selected columns)
|
||||||
foreach ($columns as $column_name => $column_index) {
|
foreach ($selected_indices as $col_index) {
|
||||||
$result['headers'][$column_name] = isset($row[$column_index]) ? trim($row[$column_index]) : '';
|
$raw = isset($row[$col_index]) ? trim((string) $row[$col_index]) : '';
|
||||||
|
// Fallback to column letter if empty
|
||||||
|
$header = $raw !== '' ? $raw : $this->indexToColumnLetter($col_index);
|
||||||
|
$result['headers'][$col_index] = $this->normalizeHeader($header);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Data rows
|
$row_assoc = [];
|
||||||
$row_data = [
|
|
||||||
'row' => $row_index + 1 // 1-based row number
|
|
||||||
];
|
|
||||||
|
|
||||||
$has_data = false;
|
$has_data = false;
|
||||||
foreach ($columns as $column_name => $column_index) {
|
foreach ($selected_indices as $col_index) {
|
||||||
$value = isset($row[$column_index]) ? trim($row[$column_index]) : '';
|
$header = $result['headers'][$col_index] ?? $this->normalizeHeader($this->indexToColumnLetter($col_index));
|
||||||
$row_data[$column_name] = $value;
|
$value = isset($row[$col_index]) ? trim((string) $row[$col_index]) : '';
|
||||||
|
$row_assoc[$header] = ($value === '') ? null : $value;
|
||||||
if ($value !== '') {
|
if ($value !== '') {
|
||||||
$has_data = true;
|
$has_data = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only add row if it has at least one non-empty value
|
|
||||||
if ($has_data) {
|
if ($has_data) {
|
||||||
$result['data'][] = $row_data;
|
$result['data'][] = $row_assoc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::info("REALISASI PAD Multiple Columns Data", [
|
|
||||||
'sheet_name' => 'REALISASI PAD',
|
|
||||||
'columns' => array_keys($columns),
|
|
||||||
'headers' => $result['headers'],
|
|
||||||
'total_data_rows' => count($result['data']),
|
|
||||||
'sample_data' => array_slice($result['data'], 0, 5), // Show first 5 rows as sample
|
|
||||||
'column_indices' => $columns
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error("Error getting REALISASI PAD data", ['error' => $e->getMessage()]);
|
Log::error("Error getting sheet data with headers", [
|
||||||
|
'sheet_name' => $sheet_name,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a column letter (e.g., 'A', 'Z', 'AA', 'AX', 'BX') to a zero-based index (A=0)
|
||||||
|
*/
|
||||||
|
private function columnLetterToIndex(string $letter): int
|
||||||
|
{
|
||||||
|
$letter = strtoupper(trim($letter));
|
||||||
|
$length = strlen($letter);
|
||||||
|
$index = 0;
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$index = $index * 26 + (ord($letter[$i]) - ord('A') + 1);
|
||||||
|
}
|
||||||
|
return $index - 1; // zero-based
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert zero-based column index to column letter (0='A')
|
||||||
|
*/
|
||||||
|
private function indexToColumnLetter(int $index): string
|
||||||
|
{
|
||||||
|
$index += 1; // make 1-based for calculation
|
||||||
|
$letters = '';
|
||||||
|
while ($index > 0) {
|
||||||
|
$mod = ($index - 1) % 26;
|
||||||
|
$letters = chr($mod + ord('A')) . $letters;
|
||||||
|
$index = intdiv($index - 1, 26);
|
||||||
|
}
|
||||||
|
return $letters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand a column range like 'A'..'AX' to zero-based indices array
|
||||||
|
*/
|
||||||
|
private function expandColumnRangeToIndices(string $start_letter, string $end_letter): array
|
||||||
|
{
|
||||||
|
$start = $this->columnLetterToIndex($start_letter);
|
||||||
|
$end = $this->columnLetterToIndex($end_letter);
|
||||||
|
if ($start > $end) {
|
||||||
|
[$start, $end] = [$end, $start];
|
||||||
|
}
|
||||||
|
return range($start, $end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize header: trim, lowercase, replace spaces with underscore, remove non-alnum/underscore
|
||||||
|
*/
|
||||||
|
private function normalizeHeader(string $header): string
|
||||||
|
{
|
||||||
|
$header = trim($header);
|
||||||
|
$header = strtolower($header);
|
||||||
|
$header = preg_replace('/\s+/', '_', $header);
|
||||||
|
$header = preg_replace('/[^a-z0-9_]/', '', $header);
|
||||||
|
return $header;
|
||||||
|
}
|
||||||
|
|
||||||
public function sync_pbg_task_payments(){
|
public function sync_pbg_task_payments(){
|
||||||
try {
|
try {
|
||||||
// Get payment data from REALISASI PAD sheet
|
$sheetName = 'Data';
|
||||||
$payment_data = $this->get_realisasi_pad_data();
|
$startLetter = 'A';
|
||||||
|
$endLetter = 'AX';
|
||||||
if (empty($payment_data['data'])) {
|
$extraLetters = ['BF'];
|
||||||
Log::warning("No payment data found to sync");
|
|
||||||
return ['success' => false, 'message' => 'No payment data found'];
|
// Fetch header row only (row 1) across A..BF and build header/selection
|
||||||
|
$headerRange = sprintf('%s!%s1:%s1', $sheetName, $startLetter, 'BF');
|
||||||
|
$headerResponse = $this->service->spreadsheets_values->get($this->spreadsheetID, $headerRange);
|
||||||
|
$headerRow = $headerResponse->getValues()[0] ?? [];
|
||||||
|
if (empty($headerRow)) {
|
||||||
|
Log::warning("No header row found in sheet", ['sheet' => $sheetName]);
|
||||||
|
return ['success' => false, 'message' => 'No header row found'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$successful_syncs = 0;
|
// Selected indices: A..AX plus BF
|
||||||
$failed_syncs = 0;
|
$selected_indices = $this->expandColumnRangeToIndices($startLetter, $endLetter);
|
||||||
$not_found_tasks = 0;
|
foreach ($extraLetters as $letter) {
|
||||||
$not_found_tasks_registration_number = [];
|
$selected_indices[] = $this->columnLetterToIndex($letter);
|
||||||
$failed_sync_registration_numbers = [];
|
}
|
||||||
$errors = [];
|
$selected_indices = array_values(array_unique($selected_indices));
|
||||||
|
sort($selected_indices);
|
||||||
|
|
||||||
foreach ($payment_data['data'] as $row) {
|
// Build normalized headers map (index -> header)
|
||||||
try {
|
$headers = [];
|
||||||
// Clean registration number from column C
|
foreach ($selected_indices as $colIdx) {
|
||||||
$registration_number = \App\Models\PbgTaskPayment::cleanRegistrationNumber($row['C']);
|
$raw = isset($headerRow[$colIdx]) ? trim((string) $headerRow[$colIdx]) : '';
|
||||||
|
$header = $raw !== '' ? $raw : $this->indexToColumnLetter($colIdx);
|
||||||
if (empty($registration_number)) {
|
$headers[$colIdx] = $this->normalizeHeader($header);
|
||||||
$failed_syncs++;
|
|
||||||
$failed_sync_registration_numbers[] = [
|
|
||||||
'row' => $row['row'],
|
|
||||||
'registration_number' => $row['C'] ?? 'EMPTY',
|
|
||||||
'reason' => 'Empty registration number'
|
|
||||||
];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find PBG task by registration number
|
|
||||||
$pbg_task = \App\Models\PbgTask::where('registration_number', $registration_number)->first();
|
|
||||||
|
|
||||||
if (!$pbg_task) {
|
|
||||||
$not_found_tasks_registration_number[] = [
|
|
||||||
'row' => $row['row'],
|
|
||||||
'registration_number' => $registration_number
|
|
||||||
];
|
|
||||||
$not_found_tasks++;
|
|
||||||
Log::warning("PBG Task not found for registration number", [
|
|
||||||
'registration_number' => $registration_number,
|
|
||||||
'row' => $row['row']
|
|
||||||
]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert data types
|
|
||||||
$payment_date = \App\Models\PbgTaskPayment::convertPaymentDate($row['AK']);
|
|
||||||
$pad_amount = \App\Models\PbgTaskPayment::convertPadAmount($row['AW']);
|
|
||||||
$sts_form_number = !empty($row['AL']) ? trim($row['AL']) : null;
|
|
||||||
|
|
||||||
// Create or update payment record
|
|
||||||
\App\Models\PbgTaskPayment::updateOrCreate(
|
|
||||||
[
|
|
||||||
'pbg_task_id' => $pbg_task->id,
|
|
||||||
'registration_number' => $registration_number
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'pbg_task_uid' => $pbg_task->uuid,
|
|
||||||
'sts_form_number' => $sts_form_number,
|
|
||||||
'payment_date' => $payment_date,
|
|
||||||
'pad_amount' => $pad_amount
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$successful_syncs++;
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$failed_syncs++;
|
|
||||||
$registration_number = $row['C'] ?? 'N/A';
|
|
||||||
$failed_sync_registration_numbers[] = [
|
|
||||||
'row' => $row['row'],
|
|
||||||
'registration_number' => $registration_number,
|
|
||||||
'reason' => $e->getMessage()
|
|
||||||
];
|
|
||||||
$errors[] = [
|
|
||||||
'row' => $row['row'],
|
|
||||||
'registration_number' => $registration_number,
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
];
|
|
||||||
|
|
||||||
Log::error("Error syncing payment data for row", [
|
|
||||||
'row' => $row['row'],
|
|
||||||
'registration_number' => $registration_number,
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = [
|
// Log environment and header diagnostics
|
||||||
'success' => true,
|
Log::info('sync_pbg_task_payments: diagnostics', [
|
||||||
'total_rows' => count($payment_data['data']),
|
'spreadsheet_id' => $this->spreadsheetID,
|
||||||
'successful_syncs' => $successful_syncs,
|
'sheet' => $sheetName,
|
||||||
'failed_syncs' => $failed_syncs,
|
'selected_indices_count' => count($selected_indices)
|
||||||
'not_found_tasks' => $not_found_tasks,
|
]);
|
||||||
'not_found_tasks_registration_number' => $not_found_tasks_registration_number,
|
|
||||||
'failed_sync_registration_numbers' => $failed_sync_registration_numbers,
|
// Validate that expected headers exist after normalization before truncating table
|
||||||
'errors' => $errors
|
$expectedHeaders = [
|
||||||
|
'no','jenis_konsultasi','no_registrasi','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','krkkkpr','no_krk','lh','ska','keterangan',
|
||||||
|
'helpdesk','pj','operator_pbg','kepemilikan','potensi_taru','validasi_dinas','kategori_retribusi',
|
||||||
|
'no_urut_ba_tpt_20250001','tanggal_ba_tpt','no_urut_ba_tpa','tanggal_ba_tpa','no_urut_skrd_20250001',
|
||||||
|
'tanggal_skrd','ptsp','selesai_terbit','tanggal_pembayaran_yyyymmdd','format_sts','tahun_terbit',
|
||||||
|
'tahun_berjalan','kelurahan','kecamatan','lb','tb','jlb','unit','usulan_retribusi',
|
||||||
|
'nilai_retribusi_keseluruhan_simbg','nilai_retribusi_keseluruhan_pad','denda','usaha__non_usaha'
|
||||||
];
|
];
|
||||||
|
|
||||||
Log::info("PBG Task Payments sync completed", $result);
|
$normalizedHeaderValues = array_values($headers);
|
||||||
|
$overlap = array_intersect($expectedHeaders, $normalizedHeaderValues);
|
||||||
|
|
||||||
// Log detailed arrays for failed syncs and not found registration numbers
|
if (count($overlap) < 10) { // too few matching headers, likely wrong sheet or headers changed
|
||||||
if (!empty($failed_sync_registration_numbers)) {
|
Log::error('sync_pbg_task_payments: header mismatch detected', [
|
||||||
Log::warning("Failed Sync Registration Numbers", [
|
'expected_sample' => array_slice($expectedHeaders, 0, 15),
|
||||||
'count' => count($failed_sync_registration_numbers),
|
'found_sample' => array_slice($normalizedHeaderValues, 0, 30),
|
||||||
'details' => $failed_sync_registration_numbers
|
'match_count' => count($overlap)
|
||||||
]);
|
]);
|
||||||
|
return ['success' => false, 'message' => 'Header mismatch - aborting to prevent null inserts'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($not_found_tasks_registration_number)) {
|
// Truncate table and restart identity (only after header validation)
|
||||||
Log::warning("Not Found Registration Numbers", [
|
Schema::disableForeignKeyConstraints();
|
||||||
'count' => count($not_found_tasks_registration_number),
|
DB::table('pbg_task_payments')->truncate();
|
||||||
'details' => $not_found_tasks_registration_number
|
Schema::enableForeignKeyConstraints();
|
||||||
|
|
||||||
|
// Map header -> db column
|
||||||
|
$map = [
|
||||||
|
'no' => 'row_no',
|
||||||
|
'jenis_konsultasi' => 'consultation_type',
|
||||||
|
'no_registrasi' => 'source_registration_number',
|
||||||
|
'nama_pemilik' => 'owner_name',
|
||||||
|
'lokasi_bg' => 'building_location',
|
||||||
|
'fungsi_bg' => 'building_function',
|
||||||
|
'nama_bangunan' => 'building_name',
|
||||||
|
'tgl_permohonan' => 'application_date_raw',
|
||||||
|
'status_verifikasi' => 'verification_status',
|
||||||
|
'status_permohonan' => 'application_status',
|
||||||
|
'alamat_pemilik' => 'owner_address',
|
||||||
|
'no_hp' => 'owner_phone',
|
||||||
|
'email' => 'owner_email',
|
||||||
|
'tanggal_catatan' => 'note_date_raw',
|
||||||
|
'catatan_kekurangan_dokumen' => 'document_shortage_note',
|
||||||
|
'gambar' => 'image_url',
|
||||||
|
'krkkkpr' => 'krk_kkpr',
|
||||||
|
'no_krk' => 'krk_number',
|
||||||
|
'lh' => 'lh',
|
||||||
|
'ska' => 'ska',
|
||||||
|
'keterangan' => 'remarks',
|
||||||
|
'helpdesk' => 'helpdesk',
|
||||||
|
'pj' => 'person_in_charge',
|
||||||
|
'operator_pbg' => 'pbg_operator',
|
||||||
|
'kepemilikan' => 'ownership',
|
||||||
|
'potensi_taru' => 'taru_potential',
|
||||||
|
'validasi_dinas' => 'agency_validation',
|
||||||
|
'kategori_retribusi' => 'retribution_category',
|
||||||
|
'no_urut_ba_tpt_20250001' => 'ba_tpt_number',
|
||||||
|
'tanggal_ba_tpt' => 'ba_tpt_date_raw',
|
||||||
|
'no_urut_ba_tpa' => 'ba_tpa_number',
|
||||||
|
'tanggal_ba_tpa' => 'ba_tpa_date_raw',
|
||||||
|
'no_urut_skrd_20250001' => 'skrd_number',
|
||||||
|
'tanggal_skrd' => 'skrd_date_raw',
|
||||||
|
'ptsp' => 'ptsp_status',
|
||||||
|
'selesai_terbit' => 'issued_status',
|
||||||
|
'tanggal_pembayaran_yyyymmdd' => 'payment_date_raw',
|
||||||
|
'format_sts' => 'sts_format',
|
||||||
|
'tahun_terbit' => 'issuance_year',
|
||||||
|
'tahun_berjalan' => 'current_year',
|
||||||
|
'kelurahan' => 'village',
|
||||||
|
'kecamatan' => 'district',
|
||||||
|
'lb' => 'building_area',
|
||||||
|
'tb' => 'building_height',
|
||||||
|
'jlb' => 'floor_count',
|
||||||
|
'unit' => 'unit_count',
|
||||||
|
'usulan_retribusi' => 'proposed_retribution',
|
||||||
|
'nilai_retribusi_keseluruhan_simbg' => 'retribution_total_simbg',
|
||||||
|
'nilai_retribusi_keseluruhan_pad' => 'retribution_total_pad',
|
||||||
|
'denda' => 'penalty_amount',
|
||||||
|
'usaha__non_usaha' => 'business_category',
|
||||||
|
];
|
||||||
|
|
||||||
|
// We'll build registration map lazily per chunk to limit memory
|
||||||
|
$regToTask = [];
|
||||||
|
|
||||||
|
// Build and insert in small batches to avoid high memory usage
|
||||||
|
$batch = [];
|
||||||
|
$inserted = 0;
|
||||||
|
// Stream rows in chunks from API to avoid loading full sheet
|
||||||
|
$rowStart = 2; // data starts from row 2
|
||||||
|
$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) {
|
||||||
|
foreach ($selected_indices as $colIdx) {
|
||||||
|
// find normalized header for this index
|
||||||
|
$h = $headers[$colIdx] ?? null;
|
||||||
|
if ($h === 'no_registrasi') {
|
||||||
|
$val = isset($row[$colIdx]) ? trim((string) $row[$colIdx]) : '';
|
||||||
|
if ($val !== '') { $chunkRegs[$val] = true; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($chunkRegs)) {
|
||||||
|
$keys = array_keys($chunkRegs);
|
||||||
|
$tasks = PbgTask::whereIn('registration_number', $keys)->get(['id','uuid','registration_number']);
|
||||||
|
foreach ($tasks as $task) {
|
||||||
|
$regToTask[trim($task->registration_number)] = ['id' => $task->id, 'uuid' => $task->uuid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build and insert this chunk
|
||||||
|
$batch = [];
|
||||||
|
foreach ($values as $rowIndex => $row) {
|
||||||
|
$record = [
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Map row values by headers
|
||||||
|
$rowByHeader = [];
|
||||||
|
foreach ($selected_indices as $colIdx) {
|
||||||
|
$h = $headers[$colIdx] ?? null;
|
||||||
|
if ($h === null) continue;
|
||||||
|
$rowByHeader[$h] = isset($row[$colIdx]) ? trim((string) $row[$colIdx]) : null;
|
||||||
|
if ($rowByHeader[$h] === '') $rowByHeader[$h] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log first non-empty row mapping for diagnostics
|
||||||
|
if ($rowIndex === 0) {
|
||||||
|
$nonEmptySample = [];
|
||||||
|
foreach ($rowByHeader as $k => $v) {
|
||||||
|
if ($v !== null && count($nonEmptySample) < 10) { $nonEmptySample[$k] = $v; }
|
||||||
|
}
|
||||||
|
Log::info('sync_pbg_task_payments: first row sample after normalization', [
|
||||||
|
'sample' => $nonEmptySample
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if this row looks like a header row
|
||||||
|
$headerCheckKeys = ['no','jenis_konsultasi','no_registrasi'];
|
||||||
|
$headerMatches = 0;
|
||||||
|
foreach ($headerCheckKeys as $hk) {
|
||||||
|
if (!array_key_exists($hk, $rowByHeader)) { continue; }
|
||||||
|
$val = $rowByHeader[$hk];
|
||||||
|
if ($val === null) { continue; }
|
||||||
|
if ($this->normalizeHeader($val) === $hk) {
|
||||||
|
$headerMatches++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($headerMatches >= 2) {
|
||||||
|
continue; // looks like a repeated header row, skip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if the entire row is empty (no values)
|
||||||
|
$hasAnyData = false;
|
||||||
|
foreach ($rowByHeader as $v) {
|
||||||
|
if ($v !== null && $v !== '') { $hasAnyData = true; break; }
|
||||||
|
}
|
||||||
|
if (!$hasAnyData) { continue; }
|
||||||
|
|
||||||
|
foreach ($map as $header => $column) {
|
||||||
|
$value = $rowByHeader[$header] ?? null;
|
||||||
|
|
||||||
|
switch ($column) {
|
||||||
|
case 'row_no':
|
||||||
|
case 'floor_count':
|
||||||
|
case 'unit_count':
|
||||||
|
case 'issuance_year':
|
||||||
|
case 'current_year':
|
||||||
|
$record[$column] = ($value === null || $value === '') ? null : (int) $value;
|
||||||
|
break;
|
||||||
|
case 'application_date_raw':
|
||||||
|
case 'note_date_raw':
|
||||||
|
case 'ba_tpt_date_raw':
|
||||||
|
case 'ba_tpa_date_raw':
|
||||||
|
case 'skrd_date_raw':
|
||||||
|
case 'payment_date_raw':
|
||||||
|
$record[$column] = $this->convertToDate($value);
|
||||||
|
break;
|
||||||
|
case 'building_area':
|
||||||
|
case 'building_height':
|
||||||
|
case 'proposed_retribution':
|
||||||
|
case 'retribution_total_simbg':
|
||||||
|
case 'retribution_total_pad':
|
||||||
|
case 'penalty_amount':
|
||||||
|
$record[$column] = $this->convertToDecimal($value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (is_string($value)) { $value = trim($value); }
|
||||||
|
$record[$column] = ($value === '' ? null : $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final trim pass
|
||||||
|
foreach ($record as $k => $v) {
|
||||||
|
if (is_string($v)) {
|
||||||
|
$t = trim($v);
|
||||||
|
$record[$k] = ($t === '') ? null : $t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve relation
|
||||||
|
$sourceReg = $rowByHeader['no_registrasi'] ?? null;
|
||||||
|
if (is_string($sourceReg)) { $sourceReg = trim($sourceReg); }
|
||||||
|
if (!empty($sourceReg) && isset($regToTask[$sourceReg])) {
|
||||||
|
$record['pbg_task_id'] = $regToTask[$sourceReg]['id'];
|
||||||
|
$record['pbg_task_uid'] = $regToTask[$sourceReg]['uuid'];
|
||||||
|
} else {
|
||||||
|
$record['pbg_task_id'] = null;
|
||||||
|
$record['pbg_task_uid'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$batch[] = $record;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($batch)) {
|
||||||
|
\App\Models\PbgTaskPayment::insert($batch);
|
||||||
|
$inserted += count($batch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// next chunk
|
||||||
|
$rowStart = $rowEnd + 1;
|
||||||
|
if (function_exists('gc_collect_cycles')) { gc_collect_cycles(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
Log::info('PBG Task Payments reloaded from sheet', ['inserted' => $inserted]);
|
||||||
|
|
||||||
|
return ['success' => true, 'inserted' => $inserted];
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error("Error syncing PBG task payments", ['error' => $e->getMessage()]);
|
Log::error("Error syncing PBG task payments", ['error' => $e->getMessage()]);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Models\GlobalSetting;
|
use App\Models\GlobalSetting;
|
||||||
|
use App\Models\PbgStatus;
|
||||||
use App\Models\PbgTask;
|
use App\Models\PbgTask;
|
||||||
use App\Models\PbgTaskDetail;
|
use App\Models\PbgTaskDetail;
|
||||||
use App\Models\PbgTaskDetailDataList;
|
use App\Models\PbgTaskDetailDataList;
|
||||||
@@ -220,6 +221,75 @@ class ServiceTabPbgTask
|
|||||||
throw new \Exception("Failed to fetch task details for UUID {$uuid} after retries.");
|
throw new \Exception("Failed to fetch task details for UUID {$uuid} after retries.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function scraping_task_detail_status($uuid)
|
||||||
|
{
|
||||||
|
$url = "{$this->simbg_host}/api/pbg/v1/detail/{$uuid}/status/";
|
||||||
|
$options = [
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => "Bearer {$this->user_token}",
|
||||||
|
'Content-Type' => 'application/json'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$maxRetries = 3;
|
||||||
|
$initialDelay = 1;
|
||||||
|
$retriedAfter401 = false;
|
||||||
|
|
||||||
|
for ($retryCount = 0; $retryCount < $maxRetries; $retryCount++) {
|
||||||
|
try {
|
||||||
|
$response = $this->client->get($url, $options);
|
||||||
|
$responseData = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
if (empty($responseData['data']) || !is_array($responseData['data'])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $responseData['data'];
|
||||||
|
|
||||||
|
// Use the static method from PbgTaskDetail model to create/update
|
||||||
|
PbgStatus::createOrUpdateFromApi($data, $uuid);
|
||||||
|
|
||||||
|
return $responseData;
|
||||||
|
} catch (\GuzzleHttp\Exception\ClientException $e) {
|
||||||
|
if ($e->getCode() === 401 && !$retriedAfter401) {
|
||||||
|
Log::warning("401 Unauthorized - Refreshing token and retrying...");
|
||||||
|
try{
|
||||||
|
$this->refreshToken();
|
||||||
|
$options['headers']['Authorization'] = "Bearer {$this->user_token}";
|
||||||
|
$retriedAfter401 = true;
|
||||||
|
continue;
|
||||||
|
}catch(\Exception $refreshError){
|
||||||
|
Log::error("Token refresh and login failed: " . $refreshError->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (\GuzzleHttp\Exception\ServerException | \GuzzleHttp\Exception\ConnectException $e) {
|
||||||
|
if ($e->getCode() === 502) {
|
||||||
|
Log::warning("502 Bad Gateway - Retrying in {$initialDelay} seconds...");
|
||||||
|
} else {
|
||||||
|
Log::error("Network error ({$e->getCode()}) - Retrying in {$initialDelay} seconds...");
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep($initialDelay);
|
||||||
|
$initialDelay *= 2;
|
||||||
|
} catch (\GuzzleHttp\Exception\RequestException $e) {
|
||||||
|
Log::error("Request error ({$e->getCode()}): " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
} catch (\JsonException $e) {
|
||||||
|
Log::error("JSON decoding error: " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::critical("Unhandled error: " . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::error("Failed to fetch task detail status for UUID {$uuid} after {$maxRetries} retries.");
|
||||||
|
throw new \Exception("Failed to fetch task details for UUID {$uuid} after retries.");
|
||||||
|
}
|
||||||
|
|
||||||
public function scraping_task_assignments($uuid)
|
public function scraping_task_assignments($uuid)
|
||||||
{
|
{
|
||||||
$url = "{$this->simbg_host}/api/pbg/v1/list-tim-penilai/{$uuid}/?page=1&size=10";
|
$url = "{$this->simbg_host}/api/pbg/v1/list-tim-penilai/{$uuid}/?page=1&size=10";
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('pbg_statuses', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('pbg_task_uuid');
|
||||||
|
$table->integer('status');
|
||||||
|
$table->string('status_name');
|
||||||
|
$table->integer('slf_status')->nullable();
|
||||||
|
$table->string('slf_status_name')->nullable();
|
||||||
|
$table->date('due_date')->nullable();
|
||||||
|
|
||||||
|
// nested "data"
|
||||||
|
$table->string('uid')->nullable();
|
||||||
|
$table->text('note')->nullable();
|
||||||
|
$table->string('file')->nullable();
|
||||||
|
$table->date('data_due_date')->nullable();
|
||||||
|
$table->timestamp('data_created_at')->nullable();
|
||||||
|
|
||||||
|
$table->json('slf_data')->nullable(); // kalau nanti slf_data ada struktur JSON
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('pbg_statuses');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('pbg_task_payments', function (Blueprint $table) {
|
||||||
|
// Drop existing foreign key if present
|
||||||
|
if (Schema::hasColumn('pbg_task_payments', 'pbg_task_id')) {
|
||||||
|
$table->dropForeign(['pbg_task_id']);
|
||||||
|
// Make column nullable
|
||||||
|
$table->unsignedBigInteger('pbg_task_id')->nullable()->change();
|
||||||
|
// Recreate foreign key
|
||||||
|
$table->foreign('pbg_task_id')->references('id')->on('pbg_task')->cascadeOnDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop legacy columns if no longer needed
|
||||||
|
if (Schema::hasColumn('pbg_task_payments', 'registration_number')) {
|
||||||
|
$table->dropIndex(['registration_number']);
|
||||||
|
$table->dropColumn('registration_number');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('pbg_task_payments', 'sts_form_number')) {
|
||||||
|
$table->dropColumn('sts_form_number');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('pbg_task_payments', 'payment_date')) {
|
||||||
|
$table->dropColumn('payment_date');
|
||||||
|
}
|
||||||
|
if (Schema::hasColumn('pbg_task_payments', 'pad_amount')) {
|
||||||
|
$table->dropColumn('pad_amount');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make pbg_task_uid nullable
|
||||||
|
if (Schema::hasColumn('pbg_task_payments', 'pbg_task_uid')) {
|
||||||
|
$table->string('pbg_task_uid')->nullable()->change();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new columns (renamed for table conventions)
|
||||||
|
$table->integer('row_no')->nullable();
|
||||||
|
$table->string('consultation_type')->nullable();
|
||||||
|
$table->string('source_registration_number')->nullable();
|
||||||
|
$table->string('owner_name')->nullable();
|
||||||
|
$table->text('building_location')->nullable();
|
||||||
|
$table->string('building_function')->nullable();
|
||||||
|
$table->string('building_name')->nullable();
|
||||||
|
$table->date('application_date_raw')->nullable();
|
||||||
|
$table->string('verification_status')->nullable();
|
||||||
|
$table->string('application_status')->nullable();
|
||||||
|
$table->text('owner_address')->nullable();
|
||||||
|
$table->string('owner_phone')->nullable();
|
||||||
|
$table->string('owner_email')->nullable();
|
||||||
|
$table->date('note_date_raw')->nullable();
|
||||||
|
$table->text('document_shortage_note')->nullable();
|
||||||
|
$table->string('image_url')->nullable();
|
||||||
|
$table->string('krk_kkpr')->nullable();
|
||||||
|
$table->string('krk_number')->nullable();
|
||||||
|
$table->string('lh')->nullable();
|
||||||
|
$table->string('ska')->nullable();
|
||||||
|
$table->string('remarks')->nullable();
|
||||||
|
$table->string('helpdesk')->nullable();
|
||||||
|
$table->string('person_in_charge')->nullable();
|
||||||
|
$table->string('pbg_operator')->nullable();
|
||||||
|
$table->string('ownership')->nullable();
|
||||||
|
$table->string('taru_potential')->nullable();
|
||||||
|
$table->string('agency_validation')->nullable();
|
||||||
|
$table->string('retribution_category')->nullable();
|
||||||
|
$table->string('ba_tpt_number')->nullable();
|
||||||
|
$table->date('ba_tpt_date_raw')->nullable();
|
||||||
|
$table->string('ba_tpa_number')->nullable();
|
||||||
|
$table->date('ba_tpa_date_raw')->nullable();
|
||||||
|
$table->string('skrd_number')->nullable();
|
||||||
|
$table->date('skrd_date_raw')->nullable();
|
||||||
|
$table->string('ptsp_status')->nullable();
|
||||||
|
$table->string('issued_status')->nullable();
|
||||||
|
$table->date('payment_date_raw')->nullable();
|
||||||
|
$table->string('sts_format')->nullable();
|
||||||
|
$table->integer('issuance_year')->nullable();
|
||||||
|
$table->integer('current_year')->nullable();
|
||||||
|
$table->string('village')->nullable();
|
||||||
|
$table->string('district')->nullable();
|
||||||
|
$table->decimal('building_area', 18, 2)->nullable()->default(0);
|
||||||
|
$table->decimal('building_height', 18, 2)->nullable()->default(0);
|
||||||
|
$table->integer('floor_count')->nullable();
|
||||||
|
$table->integer('unit_count')->nullable();
|
||||||
|
$table->decimal('proposed_retribution', 18, 2)->nullable()->default(0);
|
||||||
|
$table->decimal('retribution_total_simbg', 18, 2)->nullable()->default(0);
|
||||||
|
$table->decimal('retribution_total_pad', 18, 2)->nullable()->default(0);
|
||||||
|
$table->decimal('penalty_amount', 18, 2)->nullable()->default(0);
|
||||||
|
$table->string('business_category')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('pbg_task_payments', function (Blueprint $table) {
|
||||||
|
if (Schema::hasColumn('pbg_task_payments', 'pbg_task_id')) {
|
||||||
|
// Drop FK, revert to not nullable, recreate FK
|
||||||
|
$table->dropForeign(['pbg_task_id']);
|
||||||
|
$table->unsignedBigInteger('pbg_task_id')->nullable(false)->change();
|
||||||
|
$table->foreign('pbg_task_id')->references('id')->on('pbg_task')->cascadeOnDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revert pbg_task_uid to not nullable
|
||||||
|
if (Schema::hasColumn('pbg_task_payments', 'pbg_task_uid')) {
|
||||||
|
$table->string('pbg_task_uid')->nullable(false)->change();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the added columns
|
||||||
|
$columns = [
|
||||||
|
'row_no','consultation_type','source_registration_number','owner_name','building_location','building_function','building_name','application_date_raw',
|
||||||
|
'verification_status','application_status','owner_address','owner_phone','owner_email','note_date_raw','document_shortage_note',
|
||||||
|
'image_url','krk_kkpr','krk_number','lh','ska','remarks','helpdesk','person_in_charge','pbg_operator','ownership','taru_potential',
|
||||||
|
'agency_validation','retribution_category','ba_tpt_number','ba_tpt_date_raw','ba_tpa_number','ba_tpa_date_raw',
|
||||||
|
'skrd_number','skrd_date_raw','ptsp_status','issued_status','payment_date_raw','sts_format','issuance_year',
|
||||||
|
'current_year','village','district','building_area','building_height','floor_count','unit_count','proposed_retribution','retribution_total_simbg',
|
||||||
|
'retribution_total_pad','penalty_amount','business_category'
|
||||||
|
];
|
||||||
|
foreach ($columns as $col) {
|
||||||
|
if (Schema::hasColumn('pbg_task_payments', $col)) {
|
||||||
|
$table->dropColumn($col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore legacy columns
|
||||||
|
if (!Schema::hasColumn('pbg_task_payments', 'registration_number')) {
|
||||||
|
$table->string('registration_number');
|
||||||
|
$table->index('registration_number');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('pbg_task_payments', 'sts_form_number')) {
|
||||||
|
$table->string('sts_form_number')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('pbg_task_payments', 'payment_date')) {
|
||||||
|
$table->date('payment_date')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('pbg_task_payments', 'pad_amount')) {
|
||||||
|
$table->decimal('pad_amount', 18, 2)->default(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -32,7 +32,7 @@ class MenuSeeder extends Seeder
|
|||||||
"sort_order" => 2,
|
"sort_order" => 2,
|
||||||
"children" => [
|
"children" => [
|
||||||
[
|
[
|
||||||
"name" => "Dashboard Pimpinan (PBG)",
|
"name" => "Dashboard Pimpinan (SIMBG)",
|
||||||
"url" => "dashboard.home",
|
"url" => "dashboard.home",
|
||||||
"icon" => null,
|
"icon" => null,
|
||||||
"sort_order" => 1,
|
"sort_order" => 1,
|
||||||
@@ -69,12 +69,6 @@ class MenuSeeder extends Seeder
|
|||||||
"icon" => null,
|
"icon" => null,
|
||||||
"sort_order" => 4,
|
"sort_order" => 4,
|
||||||
],
|
],
|
||||||
[
|
|
||||||
"name" => "Dashboard Pimpinan",
|
|
||||||
"url" => "dashboard.leader",
|
|
||||||
"icon" => null,
|
|
||||||
"sort_order" => 5,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -288,7 +282,7 @@ class MenuSeeder extends Seeder
|
|||||||
private function createOrUpdateMenu($menuData, $parentId = null){
|
private function createOrUpdateMenu($menuData, $parentId = null){
|
||||||
$menuData['parent_id'] = $parentId;
|
$menuData['parent_id'] = $parentId;
|
||||||
|
|
||||||
$menu = Menu::updateOrCreate(['name' => $menuData['name']], Arr::except($menuData, ['children']));
|
$menu = Menu::updateOrCreate(['url' => $menuData['url']], Arr::except($menuData, ['children']));
|
||||||
|
|
||||||
if(!empty($menuData['children'])){
|
if(!empty($menuData['children'])){
|
||||||
foreach($menuData['children'] as $child){
|
foreach($menuData['children'] as $child){
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class UsersRoleMenuSeeder extends Seeder
|
|||||||
// Fetch all menus in a single query and index by name
|
// Fetch all menus in a single query and index by name
|
||||||
$menus = Menu::whereIn('name', [
|
$menus = Menu::whereIn('name', [
|
||||||
'Dashboard', 'Master', 'Settings', 'Data Settings', 'Data', 'Laporan', 'Neng Bedas',
|
'Dashboard', 'Master', 'Settings', 'Data Settings', 'Data', 'Laporan', 'Neng Bedas',
|
||||||
'Approval', 'Tools', 'Dashboard Pimpinan', 'Dashboard PBG', 'Users', 'Syncronize',
|
'Approval', 'Tools', 'Users', 'Syncronize', 'Dashboard Pimpinan (SIMBG)',
|
||||||
'Menu', 'Role', 'Setting Dashboard', 'PBG', 'Reklame', 'Usaha atau Industri', 'Pariwisata',
|
'Menu', 'Role', 'Setting Dashboard', 'PBG', 'Reklame', 'Usaha atau Industri', 'Pariwisata',
|
||||||
'Lap Pariwisata', 'UMKM', 'Dashboard Potensi', 'Tata Ruang', 'PDAM', 'PETA',
|
'Lap Pariwisata', 'UMKM', 'Dashboard Potensi', 'Tata Ruang', 'PDAM', 'PETA',
|
||||||
'Lap Pimpinan', 'Dalam Sistem', 'Luar Sistem', 'Google Sheets', 'TPA TPT', 'Pajak',
|
'Lap Pimpinan', 'Dalam Sistem', 'Luar Sistem', 'Google Sheets', 'TPA TPT', 'Pajak',
|
||||||
@@ -32,14 +32,14 @@ class UsersRoleMenuSeeder extends Seeder
|
|||||||
$permissions = [
|
$permissions = [
|
||||||
'superadmin' => [
|
'superadmin' => [
|
||||||
'Dashboard', 'Master', 'Settings', 'Data Settings', 'Data', 'Laporan', 'Neng Bedas',
|
'Dashboard', 'Master', 'Settings', 'Data Settings', 'Data', 'Laporan', 'Neng Bedas',
|
||||||
'Approval', 'Tools', 'Dashboard Pimpinan', 'Dashboard PBG', 'Users', 'Syncronize',
|
'Approval', 'Tools', 'Users', 'Syncronize', 'Dashboard Pimpinan (SIMBG)',
|
||||||
'Menu', 'Role', 'Setting Dashboard', 'PBG', 'Reklame', 'Usaha atau Industri', 'Pariwisata',
|
'Menu', 'Role', 'Setting Dashboard', 'PBG', 'Reklame', 'Usaha atau Industri', 'Pariwisata',
|
||||||
'Lap Pariwisata', 'UMKM', 'Dashboard Potensi', 'Tata Ruang', 'PDAM', 'Dalam Sistem',
|
'Lap Pariwisata', 'UMKM', 'Dashboard Potensi', 'Tata Ruang', 'PDAM', 'Dalam Sistem',
|
||||||
'Luar Sistem', 'Lap Pimpinan', 'Google Sheets', 'TPA TPT', 'Approval Pejabat',
|
'Luar Sistem', 'Lap Pimpinan', 'Google Sheets', 'TPA TPT', 'Approval Pejabat',
|
||||||
'Undangan', 'Rekap Pembayaran', 'Lap Rekap Data Pembayaran', 'Lap PBG (PTSP)', 'Lap Pertumbuhan', 'Pajak'
|
'Undangan', 'Rekap Pembayaran', 'Lap Rekap Data Pembayaran', 'Lap PBG (PTSP)', 'Lap Pertumbuhan', 'Pajak'
|
||||||
],
|
],
|
||||||
'user' => ['Dashboard', 'Data', 'Laporan', 'Neng Bedas',
|
'user' => ['Dashboard', 'Data', 'Laporan', 'Neng Bedas',
|
||||||
'Approval', 'Tools', 'Dashboard Pimpinan', 'Dashboard PBG', 'Users', 'Syncronize',
|
'Approval', 'Tools', 'Users', 'Syncronize', 'Dashboard Pimpinan (SIMBG)',
|
||||||
'Menu', 'Role', 'Setting Dashboard', 'PBG', 'Reklame', 'Usaha atau Industri', 'Pariwisata',
|
'Menu', 'Role', 'Setting Dashboard', 'PBG', 'Reklame', 'Usaha atau Industri', 'Pariwisata',
|
||||||
'Lap Pariwisata', 'UMKM', 'Dashboard Potensi', 'Tata Ruang', 'PDAM', 'Dalam Sistem',
|
'Lap Pariwisata', 'UMKM', 'Dashboard Potensi', 'Tata Ruang', 'PDAM', 'Dalam Sistem',
|
||||||
'Luar Sistem', 'Lap Pimpinan', 'Google Sheets', 'TPA TPT', 'Approval Pejabat',
|
'Luar Sistem', 'Lap Pimpinan', 'Google Sheets', 'TPA TPT', 'Approval Pejabat',
|
||||||
|
|||||||
1
docker/php/memory-limit.ini
Normal file
1
docker/php/memory-limit.ini
Normal file
@@ -0,0 +1 @@
|
|||||||
|
memory_limit=512M
|
||||||
@@ -565,12 +565,11 @@ class BigData {
|
|||||||
document
|
document
|
||||||
.querySelectorAll(".document-total.chart-payment-pbg-task")
|
.querySelectorAll(".document-total.chart-payment-pbg-task")
|
||||||
.forEach((element) => {
|
.forEach((element) => {
|
||||||
// const sum = this.safeGet(
|
const sum = this.safeGet(
|
||||||
// this.resumeBigData,
|
this.resumeBigData,
|
||||||
// "pbg_task_payments.sum",
|
"pbg_task_payments.sum",
|
||||||
// 0
|
0
|
||||||
// );
|
);
|
||||||
const sum = 9559353945;
|
|
||||||
element.innerText = `Rp.${addThousandSeparators(
|
element.innerText = `Rp.${addThousandSeparators(
|
||||||
sum.toString()
|
sum.toString()
|
||||||
)}`;
|
)}`;
|
||||||
|
|||||||
@@ -306,23 +306,23 @@ class DashboardPotentialInsideSystem {
|
|||||||
safeSetText(
|
safeSetText(
|
||||||
"restoran-count-amount",
|
"restoran-count-amount",
|
||||||
addThousandSeparators(
|
addThousandSeparators(
|
||||||
(this.pajakRestoranCount * 2500000).toString()
|
(this.pajakRestoranCount * 6200000).toString()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
safeSetText("hiburan-count", this.pajakHiburanCount);
|
safeSetText("hiburan-count", this.pajakHiburanCount);
|
||||||
safeSetText(
|
safeSetText(
|
||||||
"hiburan-count-amount",
|
"hiburan-count-amount",
|
||||||
addThousandSeparators((this.pajakHiburanCount * 2500000).toString())
|
addThousandSeparators((this.pajakHiburanCount * 6200000).toString())
|
||||||
);
|
);
|
||||||
safeSetText("hotel-count", this.pajakHotelCount);
|
safeSetText("hotel-count", this.pajakHotelCount);
|
||||||
safeSetText(
|
safeSetText(
|
||||||
"hotel-count-amount",
|
"hotel-count-amount",
|
||||||
addThousandSeparators(this.pajakHotelCount * 2500000).toString()
|
addThousandSeparators(this.pajakHotelCount * 6200000).toString()
|
||||||
);
|
);
|
||||||
safeSetText("parkir-count", this.pajakParkirCount);
|
safeSetText("parkir-count", this.pajakParkirCount);
|
||||||
safeSetText(
|
safeSetText(
|
||||||
"parkir-count-amount",
|
"parkir-count-amount",
|
||||||
addThousandSeparators((this.pajakParkirCount * 2500000).toString())
|
addThousandSeparators((this.pajakParkirCount * 6200000).toString())
|
||||||
);
|
);
|
||||||
safeSetText("pdam-count", this.pdamCount);
|
safeSetText("pdam-count", this.pdamCount);
|
||||||
safeSetText(
|
safeSetText(
|
||||||
|
|||||||
@@ -113,11 +113,15 @@ class PbgTasks {
|
|||||||
{ name: "Alamat" },
|
{ name: "Alamat" },
|
||||||
"Status",
|
"Status",
|
||||||
"Jenis Fungsi",
|
"Jenis Fungsi",
|
||||||
|
{ name: "Nama Bangunan" },
|
||||||
"Jenis Konsultasi",
|
"Jenis Konsultasi",
|
||||||
{ name: "Tanggal Jatuh Tempo" },
|
{ name: "Tanggal Jatuh Tempo" },
|
||||||
{
|
{
|
||||||
name: "Retribusi",
|
name: "Retribusi",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Catatan Kekurangan Dokumen",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Aksi",
|
name: "Aksi",
|
||||||
formatter: (cell) => {
|
formatter: (cell) => {
|
||||||
@@ -211,26 +215,32 @@ class PbgTasks {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
then: (data) =>
|
then: (data) =>
|
||||||
data.data.map((item) => [
|
data.data.map((item) => {
|
||||||
item.id,
|
return [
|
||||||
item.name,
|
item.id,
|
||||||
item.owner_name,
|
item.name,
|
||||||
item.condition,
|
item.owner_name,
|
||||||
item.registration_number,
|
item.condition,
|
||||||
item.document_number,
|
item.registration_number,
|
||||||
item.address,
|
item.document_number || "-",
|
||||||
item.status_name,
|
item.address,
|
||||||
item.function_type,
|
item.status_name,
|
||||||
item.consultation_type,
|
item.function_type,
|
||||||
item.due_date,
|
item.pbg_task_detail
|
||||||
item.pbg_task_retributions
|
? item.pbg_task_detail.name_building
|
||||||
? addThousandSeparators(
|
: "-",
|
||||||
item.pbg_task_retributions
|
item.consultation_type,
|
||||||
.nilai_retribusi_bangunan
|
item.due_date,
|
||||||
)
|
item.pbg_task_retributions
|
||||||
: "-",
|
? addThousandSeparators(
|
||||||
item,
|
item.pbg_task_retributions
|
||||||
]),
|
.nilai_retribusi_bangunan
|
||||||
|
)
|
||||||
|
: "-",
|
||||||
|
item.pbg_status ? item.pbg_status.note : "-",
|
||||||
|
item,
|
||||||
|
];
|
||||||
|
}),
|
||||||
total: (data) => data.meta.total,
|
total: (data) => data.meta.total,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
312
resources/js/public-search/index.js
Normal file
312
resources/js/public-search/index.js
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
import { Grid, html } from "gridjs";
|
||||||
|
import { addThousandSeparators } from "../global-config";
|
||||||
|
|
||||||
|
class PublicSearch {
|
||||||
|
constructor() {
|
||||||
|
this.table = null;
|
||||||
|
const baseInput = document.getElementById("base_url_datatable");
|
||||||
|
this.baseUrl = baseInput ? baseInput.value.split("?")[0] : "";
|
||||||
|
this.keywordInput = document.getElementById("search_input");
|
||||||
|
this.searchButton = document.getElementById("search_button");
|
||||||
|
this.searchHeader = document.getElementById("search-header");
|
||||||
|
this.tableWrapper = document.getElementById("table-wrapper");
|
||||||
|
this.emptyState = document.getElementById("empty-state");
|
||||||
|
|
||||||
|
// Tidak inisialisasi datatable sampai ada pencarian
|
||||||
|
this.datatableUrl = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.bindSearchButton();
|
||||||
|
|
||||||
|
// Check if there's a keyword in URL
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const keyword = urlParams.get("keyword");
|
||||||
|
|
||||||
|
if (keyword && keyword.trim() !== "") {
|
||||||
|
this.keywordInput.value = keyword.trim();
|
||||||
|
this.handleSearchFromUrl(keyword.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearchFromUrl(keyword) {
|
||||||
|
// Validasi input kosong atau hanya spasi
|
||||||
|
if (!keyword || keyword.trim().length === 0) {
|
||||||
|
this.showEmptyState("Mulai Pencarian");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi minimal 3 karakter
|
||||||
|
if (keyword.trim().length < 3) {
|
||||||
|
this.showEmptyState("Minimal 3 karakter untuk pencarian");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.datatableUrl = this.buildUrl(keyword.trim());
|
||||||
|
this.showSearchResults();
|
||||||
|
this.initDatatable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bindSearchButton() {
|
||||||
|
const handleSearch = () => {
|
||||||
|
const newKeyword = this.keywordInput.value.trim();
|
||||||
|
|
||||||
|
// Validasi input kosong atau hanya spasi
|
||||||
|
if (!newKeyword || newKeyword.length === 0) {
|
||||||
|
this.showEmptyState("Mulai Pencarian");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi minimal 3 karakter (setelah trim)
|
||||||
|
if (newKeyword.length < 3) {
|
||||||
|
this.showEmptyState("Minimal 3 karakter untuk pencarian");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Update datatable URL and reload
|
||||||
|
this.datatableUrl = this.buildUrl(newKeyword);
|
||||||
|
this.showSearchResults();
|
||||||
|
this.initDatatable();
|
||||||
|
|
||||||
|
// 2. Update URL query string (tanpa reload page)
|
||||||
|
const newUrl = `${window.location.pathname}${
|
||||||
|
newKeyword ? `?keyword=${encodeURIComponent(newKeyword)}` : ""
|
||||||
|
}`;
|
||||||
|
window.history.pushState({ path: newUrl }, "", newUrl);
|
||||||
|
|
||||||
|
// 3. Update visible keyword text di <em>{{ $keyword }}</em>>
|
||||||
|
const keywordDisplay = document.querySelector(".qs-header em");
|
||||||
|
if (keywordDisplay) {
|
||||||
|
keywordDisplay.textContent = newKeyword || "-";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.searchButton.addEventListener("click", handleSearch);
|
||||||
|
|
||||||
|
this.keywordInput.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle input change untuk real-time validation
|
||||||
|
this.keywordInput.addEventListener("input", (event) => {
|
||||||
|
const value = event.target.value.trim();
|
||||||
|
|
||||||
|
// Remove existing classes
|
||||||
|
this.keywordInput.classList.remove("valid", "warning", "invalid");
|
||||||
|
|
||||||
|
// Jika input kosong atau hanya spasi, show empty state
|
||||||
|
if (!value || value.length === 0) {
|
||||||
|
this.showEmptyState("Mulai Pencarian");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika kurang dari 3 karakter, show warning
|
||||||
|
if (value.length < 3) {
|
||||||
|
this.showEmptyState("Minimal 3 karakter untuk pencarian");
|
||||||
|
this.keywordInput.classList.add("warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika valid, add valid class
|
||||||
|
this.keywordInput.classList.add("valid");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle input focus untuk clear warning state
|
||||||
|
this.keywordInput.addEventListener("focus", () => {
|
||||||
|
const value = this.keywordInput.value.trim();
|
||||||
|
if (value.length >= 3) {
|
||||||
|
// Jika sudah valid, hide empty state
|
||||||
|
this.emptyState.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle input blur untuk final validation
|
||||||
|
this.keywordInput.addEventListener("blur", () => {
|
||||||
|
const value = this.keywordInput.value.trim();
|
||||||
|
if (!value || value.length === 0) {
|
||||||
|
this.showEmptyState("Mulai Pencarian");
|
||||||
|
} else if (value.length < 3) {
|
||||||
|
this.showEmptyState("Minimal 3 karakter untuk pencarian");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle Escape key untuk clear search
|
||||||
|
this.keywordInput.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
this.clearSearch();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
buildUrl(keyword) {
|
||||||
|
const url = new URL(this.baseUrl, window.location.origin);
|
||||||
|
|
||||||
|
// Validasi keyword tidak kosong dan tidak hanya spasi
|
||||||
|
if (keyword && keyword.trim() !== "" && keyword.trim().length >= 3) {
|
||||||
|
url.searchParams.set("search", keyword.trim());
|
||||||
|
} else {
|
||||||
|
url.searchParams.delete("search"); // pastikan tidak ada search param
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
showSearchResults() {
|
||||||
|
this.searchHeader.style.display = "block";
|
||||||
|
this.tableWrapper.style.display = "block";
|
||||||
|
this.emptyState.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
showEmptyState(message = "Tidak ada data yang ditemukan") {
|
||||||
|
this.searchHeader.style.display = "none";
|
||||||
|
this.tableWrapper.style.display = "none";
|
||||||
|
this.emptyState.style.display = "block";
|
||||||
|
|
||||||
|
// Update empty state message and icon
|
||||||
|
const emptyStateTitle = this.emptyState.querySelector("h4");
|
||||||
|
const emptyStateDesc = this.emptyState.querySelector("p");
|
||||||
|
const emptyIcon = this.emptyState.querySelector(".empty-icon i");
|
||||||
|
|
||||||
|
if (emptyStateTitle) {
|
||||||
|
emptyStateTitle.textContent = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emptyStateDesc) {
|
||||||
|
if (message === "Mulai Pencarian") {
|
||||||
|
emptyStateDesc.textContent =
|
||||||
|
"Masukkan kata kunci minimal 3 karakter untuk mencari data PBG";
|
||||||
|
} else if (message === "Minimal 3 karakter untuk pencarian") {
|
||||||
|
emptyStateDesc.textContent =
|
||||||
|
"Masukkan kata kunci minimal 3 karakter untuk mencari data PBG";
|
||||||
|
} else {
|
||||||
|
emptyStateDesc.textContent =
|
||||||
|
"Coba gunakan kata kunci yang berbeda atau lebih spesifik";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update icon based on message
|
||||||
|
if (emptyIcon) {
|
||||||
|
if (message === "Mulai Pencarian") {
|
||||||
|
emptyIcon.className = "fas fa-search fa-3x text-muted";
|
||||||
|
} else if (message === "Minimal 3 karakter untuk pencarian") {
|
||||||
|
emptyIcon.className =
|
||||||
|
"fas fa-exclamation-triangle fa-3x text-warning";
|
||||||
|
} else {
|
||||||
|
emptyIcon.className = "fas fa-search fa-3x text-muted";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing table if any
|
||||||
|
if (this.table) {
|
||||||
|
this.table.destroy();
|
||||||
|
this.table = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSearch() {
|
||||||
|
this.keywordInput.value = "";
|
||||||
|
this.showEmptyState("Mulai Pencarian");
|
||||||
|
|
||||||
|
// Reset CSS classes
|
||||||
|
this.keywordInput.classList.remove("valid", "warning", "invalid");
|
||||||
|
|
||||||
|
// Clear URL parameter
|
||||||
|
const newUrl = window.location.pathname;
|
||||||
|
window.history.pushState({ path: newUrl }, "", newUrl);
|
||||||
|
|
||||||
|
// Reset datatable URL
|
||||||
|
this.datatableUrl = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
initDatatable() {
|
||||||
|
const tableContainer = document.getElementById(
|
||||||
|
"datatable-public-search"
|
||||||
|
);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
columns: [
|
||||||
|
{ name: "ID", width: "80px" },
|
||||||
|
{ name: "Nama Pemohon", width: "150px" },
|
||||||
|
{ name: "Nama Pemilik", width: "150px" },
|
||||||
|
{ name: "Kondisi", width: "120px" },
|
||||||
|
{ name: "Nomor Registrasi", width: "180px" },
|
||||||
|
{ name: "Status", width: "120px" },
|
||||||
|
{ name: "Jenis Fungsi", width: "150px" },
|
||||||
|
{ name: "Nama Bangunan", width: "200px" },
|
||||||
|
{ name: "Jenis Konsultasi", width: "150px" },
|
||||||
|
{ name: "Tanggal Jatuh Tempo", width: "140px" },
|
||||||
|
{ name: "Retribusi", width: "120px" },
|
||||||
|
{ name: "Catatan Kekurangan Dokumen", width: "120px" },
|
||||||
|
],
|
||||||
|
search: false,
|
||||||
|
pagination: {
|
||||||
|
limit: 15,
|
||||||
|
server: {
|
||||||
|
url: (prev, page) =>
|
||||||
|
`${prev}${prev.includes("?") ? "&" : "?"}page=${
|
||||||
|
page + 1
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort: true,
|
||||||
|
server: {
|
||||||
|
url: this.datatableUrl,
|
||||||
|
then: (data) => {
|
||||||
|
// Check if data is empty
|
||||||
|
if (!data.data || data.data.length === 0) {
|
||||||
|
this.showEmptyState(
|
||||||
|
data.message || "Tidak ada data yang ditemukan"
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.data.map((item) => [
|
||||||
|
item.id || "-",
|
||||||
|
item.name || "-",
|
||||||
|
item.owner_name || "-",
|
||||||
|
item.condition || "-",
|
||||||
|
item.registration_number || "-",
|
||||||
|
item.status_name || "-",
|
||||||
|
item.function_type || "-",
|
||||||
|
item.name_building || "-",
|
||||||
|
item.consultation_type || "-",
|
||||||
|
item.due_date || "-",
|
||||||
|
item.nilai_retribusi_bangunan
|
||||||
|
? addThousandSeparators(
|
||||||
|
item.nilai_retribusi_bangunan
|
||||||
|
)
|
||||||
|
: "-",
|
||||||
|
item.note || "-",
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
total: (data) => data.total || 0,
|
||||||
|
error: (error) => {
|
||||||
|
console.error("Datatable error:", error);
|
||||||
|
this.showEmptyState(
|
||||||
|
"Terjadi kesalahan saat mengambil data"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.table) {
|
||||||
|
this.table
|
||||||
|
.updateConfig({
|
||||||
|
...config,
|
||||||
|
server: { ...config.server, url: this.datatableUrl },
|
||||||
|
})
|
||||||
|
.forceRender();
|
||||||
|
} else {
|
||||||
|
tableContainer.innerHTML = "";
|
||||||
|
this.table = new Grid(config).render(tableContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const app = new PublicSearch();
|
||||||
|
app.init();
|
||||||
|
});
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Grid, html } from "gridjs";
|
import { Grid, html } from "gridjs";
|
||||||
|
import { addThousandSeparators } from "../global-config";
|
||||||
|
|
||||||
class QuickSearchResult {
|
class QuickSearchResult {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -62,15 +63,19 @@ class QuickSearchResult {
|
|||||||
const config = {
|
const config = {
|
||||||
columns: [
|
columns: [
|
||||||
"ID",
|
"ID",
|
||||||
{ name: "Name" },
|
{ name: "Nama Pemohon" },
|
||||||
{ name: "Condition" },
|
{ name: "Nama Pemilik" },
|
||||||
"Registration Number",
|
{ name: "Kondisi" },
|
||||||
"Document Number",
|
"Nomor Registrasi",
|
||||||
{ name: "Address" },
|
"Nomor Dokumen",
|
||||||
|
{ name: "Alamat" },
|
||||||
"Status",
|
"Status",
|
||||||
"Function Type",
|
"Jenis Fungsi",
|
||||||
"Consultation Type",
|
{ name: "Nama Bangunan" },
|
||||||
{ name: "Due Date" },
|
"Jenis Konsultasi",
|
||||||
|
{ name: "Tanggal Jatuh Tempo" },
|
||||||
|
{ name: "Retribusi" },
|
||||||
|
{ name: "Catatan Kekurangan Dokumen" },
|
||||||
{
|
{
|
||||||
name: "Action",
|
name: "Action",
|
||||||
formatter: (cell) => {
|
formatter: (cell) => {
|
||||||
@@ -101,14 +106,18 @@ class QuickSearchResult {
|
|||||||
data.data.map((item) => [
|
data.data.map((item) => [
|
||||||
item.id,
|
item.id,
|
||||||
item.name,
|
item.name,
|
||||||
|
item.owner_name,
|
||||||
item.condition,
|
item.condition,
|
||||||
item.registration_number,
|
item.registration_number,
|
||||||
item.document_number,
|
item.document_number,
|
||||||
item.address,
|
item.address,
|
||||||
item.status_name,
|
item.status_name,
|
||||||
item.function_type,
|
item.function_type,
|
||||||
|
item.name_building,
|
||||||
item.consultation_type,
|
item.consultation_type,
|
||||||
item.due_date,
|
item.due_date,
|
||||||
|
addThousandSeparators(item.nilai_retribusi_bangunan),
|
||||||
|
item.note || "-",
|
||||||
item,
|
item,
|
||||||
]),
|
]),
|
||||||
total: (data) => data.total,
|
total: (data) => data.total,
|
||||||
|
|||||||
@@ -216,7 +216,9 @@ class SyncronizeTask {
|
|||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.toastMessage.innerText =
|
this.toastMessage.innerText =
|
||||||
data.data.message || "Synchronize successfully!";
|
data?.data?.message ||
|
||||||
|
data?.message ||
|
||||||
|
"Synchronize successfully!";
|
||||||
this.toast.show();
|
this.toast.show();
|
||||||
|
|
||||||
// Update the table if it exists
|
// Update the table if it exists
|
||||||
|
|||||||
281
resources/scss/pages/public-search/index.scss
Normal file
281
resources/scss/pages/public-search/index.scss
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
.qs-wrapper {
|
||||||
|
padding: 20px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qs-toolbar {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qs-search-form {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.gsp-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #007bff;
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:invalid {
|
||||||
|
border-color: #dc3545;
|
||||||
|
box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style untuk input yang valid
|
||||||
|
&.valid {
|
||||||
|
border-color: #28a745;
|
||||||
|
box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style untuk input yang warning
|
||||||
|
&.warning {
|
||||||
|
border-color: #ffc107;
|
||||||
|
box-shadow: 0 0 0 3px rgba(255, 193, 7, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gsp-btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.qs-header {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
border-left: 4px solid #007bff;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #6c757d;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.qs-table-wrapper {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qs-empty-state {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 60px 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
color: #dee2e6;
|
||||||
|
|
||||||
|
i {
|
||||||
|
opacity: 0.7;
|
||||||
|
|
||||||
|
&.text-warning {
|
||||||
|
color: #ffc107 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GridJS customization
|
||||||
|
.gridjs-wrapper {
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridjs-table {
|
||||||
|
border: none !important;
|
||||||
|
width: 100% !important;
|
||||||
|
table-layout: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure table cells don't wrap unnecessarily
|
||||||
|
.gridjs-td {
|
||||||
|
border-bottom: 1px solid #e9ecef !important;
|
||||||
|
padding: 16px 12px !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
text-overflow: ellipsis !important;
|
||||||
|
max-width: 200px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow specific columns to wrap if needed
|
||||||
|
.gridjs-td:nth-child(4), // Kondisi
|
||||||
|
.gridjs-td:nth-child(7), // Jenis Fungsi
|
||||||
|
.gridjs-td:nth-child(8), // Nama Bangunan
|
||||||
|
.gridjs-td:nth-child(9), // Jenis Konsultasi
|
||||||
|
.gridjs-td:nth-child(10) {
|
||||||
|
// Tanggal Jatuh Tempo
|
||||||
|
white-space: normal !important;
|
||||||
|
word-wrap: break-word !important;
|
||||||
|
max-width: 150px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridjs-th {
|
||||||
|
background: #f8f9fa !important;
|
||||||
|
border-bottom: 2px solid #dee2e6 !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
color: #495057 !important;
|
||||||
|
padding: 16px 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridjs-td {
|
||||||
|
border-bottom: 1px solid #e9ecef !important;
|
||||||
|
padding: 16px 12px !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridjs-pagination {
|
||||||
|
border-top: 1px solid #e9ecef !important;
|
||||||
|
padding: 20px !important;
|
||||||
|
|
||||||
|
.gridjs-pages {
|
||||||
|
button {
|
||||||
|
border: 1px solid #dee2e6 !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
margin: 0 2px !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e9ecef !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.gridjs-currentPage {
|
||||||
|
background: #007bff !important;
|
||||||
|
color: white !important;
|
||||||
|
border-color: #007bff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive design
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.qs-wrapper {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qs-toolbar {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qs-search-form {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
.gsp-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gsp-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.qs-header {
|
||||||
|
padding: 15px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.qs-empty-state {
|
||||||
|
padding: 40px 15px;
|
||||||
|
|
||||||
|
.empty-icon i {
|
||||||
|
font-size: 2.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table responsive improvements
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.gridjs-wrapper {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridjs-table {
|
||||||
|
min-width: 1000px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure full width on larger screens
|
||||||
|
@media (min-width: 1201px) {
|
||||||
|
.qs-wrapper {
|
||||||
|
padding: 20px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridjs-wrapper {
|
||||||
|
max-width: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,30 @@
|
|||||||
color: #000; // black text
|
color: #000; // black text
|
||||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
|
||||||
|
// Empty state styles
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 1rem;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 3rem;
|
||||||
|
color: #6c757d;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: #6c757d;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
color: #6c757d;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|
||||||
|
|||||||
@@ -157,7 +157,7 @@
|
|||||||
@endcomponent
|
@endcomponent
|
||||||
|
|
||||||
@component('components.circle', [
|
@component('components.circle', [
|
||||||
'document_title' => 'Berkas Terverifikasi',
|
'document_title' => 'Berkas Lengkap',
|
||||||
'document_color' => '#5170ff',
|
'document_color' => '#5170ff',
|
||||||
'document_type' => 'Berkas',
|
'document_type' => 'Berkas',
|
||||||
'document_id' => 'chart-berkas-terverifikasi',
|
'document_id' => 'chart-berkas-terverifikasi',
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@component('components.circle', [
|
@component('components.circle', [
|
||||||
'document_title' => 'Berkas Belum Terverifikasi',
|
'document_title' => 'Berkas Belum Lengkap',
|
||||||
'document_color' => '#5170ff',
|
'document_color' => '#5170ff',
|
||||||
'document_type' => 'Berkas',
|
'document_type' => 'Berkas',
|
||||||
'document_id' => 'chart-berkas-belum-terverifikasi',
|
'document_id' => 'chart-berkas-belum-terverifikasi',
|
||||||
@@ -192,7 +192,7 @@
|
|||||||
|
|
||||||
|
|
||||||
@component('components.circle',[
|
@component('components.circle',[
|
||||||
'document_title' => 'Realisasi Terbit PBG',
|
'document_title' => 'Realisasi PAD PBG',
|
||||||
'document_color' => '#8cc540',
|
'document_color' => '#8cc540',
|
||||||
'document_type' => 'Berkas',
|
'document_type' => 'Berkas',
|
||||||
'document_id' => 'chart-realisasi-tebit-pbg',
|
'document_id' => 'chart-realisasi-tebit-pbg',
|
||||||
@@ -203,7 +203,7 @@
|
|||||||
@endcomponent
|
@endcomponent
|
||||||
|
|
||||||
@component('components.circle',[
|
@component('components.circle',[
|
||||||
'document_title' => 'Pembayaran Realisasi PBG',
|
'document_title' => 'Realisasi PAD',
|
||||||
'document_color' => '#0a0099',
|
'document_color' => '#0a0099',
|
||||||
'document_type' => 'Berkas',
|
'document_type' => 'Berkas',
|
||||||
'document_id' => 'chart-payment-pbg-task',
|
'document_id' => 'chart-payment-pbg-task',
|
||||||
|
|||||||
@@ -17,10 +17,22 @@
|
|||||||
<li class="menu-title">Menu</li>
|
<li class="menu-title">Menu</li>
|
||||||
|
|
||||||
@php
|
@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) {
|
function renderMenu($menus) {
|
||||||
|
$currentMenuId = request('menu_id');
|
||||||
foreach ($menus as $menu) {
|
foreach ($menus as $menu) {
|
||||||
$collapseId = "sidebar-" . $menu->id;
|
$collapseId = "sidebar-" . $menu->id;
|
||||||
$hasChildren = $menu->children->count() > 0;
|
$hasChildren = $menu->children->count() > 0;
|
||||||
|
$isActive = isActiveMenu($menu, $currentMenuId);
|
||||||
|
|
||||||
// Pastikan route tersedia dan boleh ditampilkan
|
// Pastikan route tersedia dan boleh ditampilkan
|
||||||
$menuUrl = '#';
|
$menuUrl = '#';
|
||||||
@@ -28,14 +40,14 @@
|
|||||||
if (Route::has($menu->url)) {
|
if (Route::has($menu->url)) {
|
||||||
$menuUrl = route($menu->url, ['menu_id' => $menu->id]);
|
$menuUrl = route($menu->url, ['menu_id' => $menu->id]);
|
||||||
} else {
|
} 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 '<li class="nav-item ' . ($hasChildren ? 'has-children' : '') . ' ' . ($isActive ? 'active' : '') . '">';
|
||||||
echo '<a class="nav-link ' . ($hasChildren ? 'menu-arrow' : '') . '"
|
echo '<a class="nav-link ' . ($hasChildren ? 'menu-arrow' : '') . ' ' . ($isActive ? 'active' : '') . '"
|
||||||
href="' . ($hasChildren ? "#$collapseId" : $menuUrl) . '"
|
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
|
// Tampilkan ikon hanya jika tersedia
|
||||||
if (!empty($menu->icon)) {
|
if (!empty($menu->icon)) {
|
||||||
@@ -48,7 +60,7 @@
|
|||||||
echo '</a>';
|
echo '</a>';
|
||||||
|
|
||||||
if ($hasChildren) {
|
if ($hasChildren) {
|
||||||
echo '<div class="collapse" id="' . $collapseId . '">
|
echo '<div class="collapse ' . ($isActive ? 'show' : '') . '" id="' . $collapseId . '">
|
||||||
<ul class="nav sub-navbar-nav">';
|
<ul class="nav sub-navbar-nav">';
|
||||||
renderMenu($menu->children);
|
renderMenu($menu->children);
|
||||||
echo '</ul></div>';
|
echo '</ul></div>';
|
||||||
@@ -72,4 +84,52 @@
|
|||||||
@for ($i = 0; $i < 20; $i++)
|
@for ($i = 0; $i < 20; $i++)
|
||||||
<div class="shooting-star"></div>
|
<div class="shooting-star"></div>
|
||||||
@endfor
|
@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>
|
class="fs-24 align-middle"></iconify-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="d-flex align-items-center gap-2">
|
<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">
|
<div class="topbar-item">
|
||||||
<a href="{{ route('chatbot.index') }}" class="topbar-button">
|
<a href="{{ route('chatbot.index') }}" class="topbar-button">
|
||||||
@@ -37,118 +19,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</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 -->
|
<!-- User -->
|
||||||
<div class="dropdown topbar-item">
|
<div class="dropdown topbar-item">
|
||||||
<a type="button" class="topbar-button" id="page-header-user-dropdown" data-bs-toggle="dropdown"
|
<a type="button" class="topbar-button" id="page-header-user-dropdown" data-bs-toggle="dropdown"
|
||||||
@@ -162,28 +32,6 @@
|
|||||||
<!-- item-->
|
<!-- item-->
|
||||||
<h6 class="dropdown-header">{{ Auth::user()->email }}</h6>
|
<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>
|
<div class="dropdown-divider my-1"></div>
|
||||||
|
|
||||||
<form id="logout-form" action="{{route('logout')}}" method="POST" style="display: none;">
|
<form id="logout-form" action="{{route('logout')}}" method="POST" style="display: none;">
|
||||||
@@ -200,12 +48,60 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</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>
|
<script>
|
||||||
function logoutUser() {
|
function logoutUser() {
|
||||||
// Hapus token dari localStorage
|
// Hapus token dari localStorage
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
|
|
||||||
// Submit form logout Laravel
|
// Submit form logout Laravel
|
||||||
document.getElementById('logout-form').submit();
|
document.getElementById('logout-form').submit();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -112,6 +112,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if ($data->pbg_status)
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Keterangan</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="note" class="form-label">Note</label>
|
||||||
|
<p class="form-control-plaintext mb-0">{{$data->pbg_status->note}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -208,10 +225,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="row">
|
<div class="empty-state">
|
||||||
<div class="col-md-12">
|
<i class="fas fa-folder-open empty-icon"></i>
|
||||||
Data Not Available
|
<h5 class="empty-title">Data Tidak Tersedia</h5>
|
||||||
</div>
|
<p class="empty-text">Tidak ada data yang terkait dengan PBG task ini.</p>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@@ -250,10 +267,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="row">
|
<div class="empty-state">
|
||||||
<div class="col-md-12">
|
<i class="fas fa-folder-open empty-icon"></i>
|
||||||
Data Not Available
|
<h5 class="empty-title">Data Tidak Tersedia</h5>
|
||||||
</div>
|
<p class="empty-text">Tidak ada data yang terkait dengan PBG task ini.</p>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@@ -293,10 +310,10 @@
|
|||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
@else
|
@else
|
||||||
<div class="row">
|
<div class="empty-state">
|
||||||
<div class="col-md-12">
|
<i class="fas fa-folder-open empty-icon"></i>
|
||||||
Data Not Available
|
<h5 class="empty-title">Data Tidak Tersedia</h5>
|
||||||
</div>
|
<p class="empty-text">Tidak ada data yang terkait dengan PBG task ini.</p>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@@ -411,10 +428,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="row">
|
<div class="empty-state">
|
||||||
<div class="col-md-12">
|
<i class="fas fa-folder-open empty-icon"></i>
|
||||||
Data Not Available
|
<h5 class="empty-title">Data Tidak Tersedia</h5>
|
||||||
</div>
|
<p class="empty-text">Tidak ada data yang terkait dengan PBG task ini.</p>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@@ -506,8 +523,8 @@
|
|||||||
@else
|
@else
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<i class="fas fa-folder-open empty-icon"></i>
|
<i class="fas fa-folder-open empty-icon"></i>
|
||||||
<h5 class="empty-title">No Data Lists Available</h5>
|
<h5 class="empty-title">Data Tidak Tersedia</h5>
|
||||||
<p class="empty-text">There are no data lists associated with this PBG task.</p>
|
<p class="empty-text">Tidak ada data yang terkait dengan PBG task ini.</p>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
47
resources/views/public-search/index.blade.php
Normal file
47
resources/views/public-search/index.blade.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
@extends('layouts.base', ['subtitle' => 'Quick Search'])
|
||||||
|
|
||||||
|
@section('css')
|
||||||
|
@vite(['resources/scss/pages/public-search/index.scss'])
|
||||||
|
@vite(['node_modules/gridjs/dist/theme/mermaid.min.css'])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<input type="hidden" value="{{ route('public-search-datatable') }}" id="base_url_datatable" />
|
||||||
|
|
||||||
|
<div class="qs-wrapper">
|
||||||
|
<div class="qs-toolbar d-flex justify-content-between align-items-center pt-4 pb-4">
|
||||||
|
<!-- Search Area (no form action) -->
|
||||||
|
<div class="qs-search-form d-flex align-items-center">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="search_input"
|
||||||
|
class="gsp-input me-2"
|
||||||
|
placeholder="Cari data..."
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button type="button" id="search_button" class="gsp-btn">Cari</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="qs-header mb-3" id="search-header" style="display: none;">
|
||||||
|
<h2>Hasil Pencarian</h2>
|
||||||
|
<p>Berikut adalah data hasil pencarian berdasarkan kata kunci yang Anda masukkan.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="qs-table-wrapper" id="table-wrapper" style="display: none;">
|
||||||
|
<div class="p-3" id="datatable-public-search"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="qs-empty-state text-center py-5" id="empty-state">
|
||||||
|
<div class="empty-icon mb-3">
|
||||||
|
<i class="fas fa-search fa-3x text-muted"></i>
|
||||||
|
</div>
|
||||||
|
<h4 class="text-muted mb-2">Mulai Pencarian</h4>
|
||||||
|
<p class="text-muted">Masukkan kata kunci minimal 3 karakter untuk mencari data PBG</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('scripts')
|
||||||
|
@vite(['resources/js/public-search/index.js'])
|
||||||
|
@endsection
|
||||||
@@ -65,6 +65,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if ($data->pbg_status)
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-black">Catatan Kekurangan Dokumen</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="note" class="form-label text-black">Keterangan</label>
|
||||||
|
<p class="form-control-plaintext mb-0 text-black">{{$data->pbg_status->note}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -124,7 +141,8 @@
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<dl class="row mb-0">
|
<dl class="row mb-0">
|
||||||
<dt class="col-sm-4">Nilai Retribusi Bangunan</dt>
|
<dt class="col-sm-4">Nilai Retribusi Bangunan</dt>
|
||||||
<dd class="col-sm-8">{{$data->pbg_task_retributions->nilai_retribusi_bangunan ?? '-'}}</dd>
|
<dd class="col-sm-8">{{ number_format($data->pbg_task_retributions->nilai_retribusi_bangunan, 2, ',', '.') }}</dd>
|
||||||
|
|
||||||
|
|
||||||
<dt class="col-sm-4">Nilai Prasarana</dt>
|
<dt class="col-sm-4">Nilai Prasarana</dt>
|
||||||
<dd class="col-sm-8">{{$data->pbg_task_retributions->nilai_prasarana ?? '-'}}</dd>
|
<dd class="col-sm-8">{{$data->pbg_task_retributions->nilai_prasarana ?? '-'}}</dd>
|
||||||
@@ -136,13 +154,15 @@
|
|||||||
<dd class="col-sm-8">{{$data->pbg_task_retributions->underpayment ?? '-'}}</dd>
|
<dd class="col-sm-8">{{$data->pbg_task_retributions->underpayment ?? '-'}}</dd>
|
||||||
|
|
||||||
<dt class="col-sm-4">SKRD Amount</dt>
|
<dt class="col-sm-4">SKRD Amount</dt>
|
||||||
<dd class="col-sm-8">{{$data->pbg_task_retributions->skrd_amount ?? '-'}}</dd>
|
<dd class="col-sm-8">{{ number_format($data->pbg_task_retributions->skrd_amount, 2, ',', '.') }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="alert alert-secondary" role="alert">
|
<div class="empty-state">
|
||||||
Data Not Available
|
<i class="fas fa-folder-open empty-icon"></i>
|
||||||
|
<h5 class="empty-title">Data Tidak Tersedia</h5>
|
||||||
|
<p class="empty-text">Tidak ada data yang terkait dengan PBG task ini.</p>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@@ -171,8 +191,10 @@
|
|||||||
<dd class="col-sm-8">{{$data->pbg_task_index_integrations->total ?? '-'}}</dd>
|
<dd class="col-sm-8">{{$data->pbg_task_index_integrations->total ?? '-'}}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
@else
|
@else
|
||||||
<div class="alert alert-secondary" role="alert">
|
<div class="empty-state">
|
||||||
Data Not Available
|
<i class="fas fa-folder-open empty-icon"></i>
|
||||||
|
<h5 class="empty-title">Data Tidak Tersedia</h5>
|
||||||
|
<p class="empty-text">Tidak ada data yang terkait dengan PBG task ini.</p>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@@ -206,8 +228,10 @@
|
|||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
@else
|
@else
|
||||||
<div class="alert alert-secondary" role="alert">
|
<div class="empty-state">
|
||||||
Data Not Available
|
<i class="fas fa-folder-open empty-icon"></i>
|
||||||
|
<h5 class="empty-title">Data Tidak Tersedia</h5>
|
||||||
|
<p class="empty-text">Tidak ada data yang terkait dengan PBG task ini.</p>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
|
|||||||
Route::get('/scraping','index')->name('scraping');
|
Route::get('/scraping','index')->name('scraping');
|
||||||
Route::get('/retry-scraping/{id}','retry_syncjob')->name('retry-scraping');
|
Route::get('/retry-scraping/{id}','retry_syncjob')->name('retry-scraping');
|
||||||
});
|
});
|
||||||
// Route::apiResource('/scraping', ScrapingController::class);
|
|
||||||
|
|
||||||
// reklame
|
// reklame
|
||||||
Route::apiResource('advertisements', AdvertisementController::class);
|
Route::apiResource('advertisements', AdvertisementController::class);
|
||||||
@@ -118,11 +117,6 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
|
|||||||
|
|
||||||
// sync pbg google sheet
|
// sync pbg google sheet
|
||||||
Route::apiResource('/api-google-sheet', GoogleSheetController::class);
|
Route::apiResource('/api-google-sheet', GoogleSheetController::class);
|
||||||
Route::get('/sync-task', [SyncronizeController::class, 'syncPbgTask'])->name('api.task');
|
|
||||||
Route::get('/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
|
// menus api
|
||||||
Route::controller(MenusController::class)->group(function (){
|
Route::controller(MenusController::class)->group(function (){
|
||||||
|
|||||||
@@ -8,6 +8,5 @@ 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:scraping-leader-data")->dailyAt("00:00");
|
Schedule::command("app:start-scraping-data --confirm")->dailyAt("00:00");
|
||||||
Schedule::command("app:start-scraping-data --confirm")->dailyAt("00:30");
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,10 @@ use Illuminate\Support\Facades\Route;
|
|||||||
require __DIR__ . '/auth.php';
|
require __DIR__ . '/auth.php';
|
||||||
|
|
||||||
Route::get('/search', [QuickSearchController::class, 'index'])->name('search');
|
Route::get('/search', [QuickSearchController::class, 'index'])->name('search');
|
||||||
|
Route::get('/public-search', [QuickSearchController::class, 'public_search'])->name('public-search');
|
||||||
Route::get('/search-result', [QuickSearchController::class, 'search_result'])->name('search-result');
|
Route::get('/search-result', [QuickSearchController::class, 'search_result'])->name('search-result');
|
||||||
Route::get('/quick-search-datatable', [QuickSearchController::class, 'quick_search_datatable'])->name('quick-search-datatable');
|
Route::get('/quick-search-datatable', [QuickSearchController::class, 'quick_search_datatable'])->name('quick-search-datatable');
|
||||||
|
Route::get('/public-search-datatable', [QuickSearchController::class, 'public_search_datatable'])->name('public-search-datatable');
|
||||||
Route::get('/quick-search/{id}', [QuickSearchController::class, 'show'])->name('quick-search.detail');
|
Route::get('/quick-search/{id}', [QuickSearchController::class, 'show'])->name('quick-search.detail');
|
||||||
Route::get('/quick-search/{uuid}/task-assignments', [QuickSearchController::class, 'task_assignments'])->name('api.quick-search-task-assignments');
|
Route::get('/quick-search/{uuid}/task-assignments', [QuickSearchController::class, 'task_assignments'])->name('api.quick-search-task-assignments');
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
"resources/scss/pages/quick-search/detail.scss",
|
"resources/scss/pages/quick-search/detail.scss",
|
||||||
"resources/scss/pages/quick-search/index.scss",
|
"resources/scss/pages/quick-search/index.scss",
|
||||||
"resources/scss/pages/quick-search/result.scss",
|
"resources/scss/pages/quick-search/result.scss",
|
||||||
|
"resources/scss/pages/public-search/index.scss",
|
||||||
"resources/scss/pages/pbg-task/show.scss",
|
"resources/scss/pages/pbg-task/show.scss",
|
||||||
|
|
||||||
"node_modules/quill/dist/quill.snow.css",
|
"node_modules/quill/dist/quill.snow.css",
|
||||||
@@ -148,6 +149,8 @@ export default defineConfig(({ mode }) => {
|
|||||||
"resources/js/quick-search/index.js",
|
"resources/js/quick-search/index.js",
|
||||||
"resources/js/quick-search/result.js",
|
"resources/js/quick-search/result.js",
|
||||||
"resources/js/quick-search/detail.js",
|
"resources/js/quick-search/detail.js",
|
||||||
|
// public-search
|
||||||
|
"resources/js/public-search/index.js",
|
||||||
// growth-report
|
// growth-report
|
||||||
"resources/js/report/growth-report/index.js",
|
"resources/js/report/growth-report/index.js",
|
||||||
// dummy
|
// dummy
|
||||||
|
|||||||
Reference in New Issue
Block a user