Compare commits

...

9 Commits

Author SHA1 Message Date
arifal
d95676d477 fix show page document pbg 2025-04-10 15:44:51 +07:00
arifal
7737fee30f add validation topic on send prompt if not relevan 2025-04-10 01:59:57 +07:00
arifal
48293386c7 fix change chatbot to top 2025-04-09 21:22:26 +07:00
arifal
84870b95b1 add feat upload pbg task 2025-04-09 21:10:20 +07:00
arifal
6294d2f950 add new users on seeder for role user and superadmin 2025-03-27 17:19:27 +07:00
arifal
7b70591be5 add readme and backup schema local 2025-03-27 15:28:21 +07:00
arifal
3b67bfd1fb remove duplicate use timeout when running queue 2025-03-26 15:20:03 +07:00
arifal
45e22096ed add column to model import datasources 2025-03-26 11:51:57 +07:00
arifal
c7a8d6d249 add condition show button retry 2025-03-25 22:46:34 +07:00
23 changed files with 717 additions and 191 deletions

View File

@@ -17,7 +17,7 @@ sudo nano /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker] [program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d process_name=%(program_name)s_%(process_num)02d
command=php /home/arifal/development/sibedas-pbg-web/artisan queue:work --queue=default --timeout=40000 --tries=1 command=php /home/arifal/development/sibedas-pbg-web/artisan queue:work --queue=default --timeout=82800 --tries=1
autostart=true autostart=true
autorestart=true autorestart=true
numprocs=1 numprocs=1

View File

@@ -34,7 +34,7 @@ class ChatbotController extends Controller
}; };
$chatHistory = $request->input('chatHistory'); $chatHistory = $request->input('chatHistory');
// Log::info('Chat history sebelum disimpan:', ['history' => $chatHistory]); Log::info('Chat history sebelum disimpan:', ['history' => $chatHistory]);
if ($main_content === "UNKNOWN") { if ($main_content === "UNKNOWN") {
return response()->json(['response' => 'Invalid tab_active value.'], 400); return response()->json(['response' => 'Invalid tab_active value.'], 400);
@@ -44,14 +44,15 @@ class ChatbotController extends Controller
$queryResponse = $this->openAIService->generateQueryBasedMainContent($request->input('prompt'), $main_content, $chatHistory); $queryResponse = $this->openAIService->generateQueryBasedMainContent($request->input('prompt'), $main_content, $chatHistory);
$firstValidation = $this->openAIService->validateSyntaxQuery($queryResponse); if (str_contains($queryResponse, 'tidak relevan') || str_contains($queryResponse, 'tidak valid') || str_starts_with($queryResponse, 'Prompt')) {
$secondValidation = $this->openAIService->validateSyntaxQuery($queryResponse); return response()->json(['response' => $queryResponse], 400);
}
$formattedResultQuery = "[]"; $formattedResultQuery = "[]";
$queryResponse = str_replace(['```sql', '```'], '', $queryResponse); $queryResponse = str_replace(['```sql', '```'], '', $queryResponse);
$resultQuery = DB::select($queryResponse); $resultQuery = DB::select($queryResponse);
$formattedResultQuery = json_encode($resultQuery, JSON_PRETTY_PRINT); $formattedResultQuery = json_encode($resultQuery, JSON_PRETTY_PRINT);
// info($formattedResultQuery); info($formattedResultQuery);
$nlpResult = $this->openAIService->generateNLPFromQuery($request->input('prompt'), $formattedResultQuery); $nlpResult = $this->openAIService->generateNLPFromQuery($request->input('prompt'), $formattedResultQuery);
$finalGeneratedText =$this->openAIService->generateFinalText($nlpResult); $finalGeneratedText =$this->openAIService->generateFinalText($nlpResult);
@@ -92,9 +93,6 @@ class ChatbotController extends Controller
$queryResponse = $this->openAIService->createMainQuery($classifyResponse, $request->input('prompt'), $chatHistory); $queryResponse = $this->openAIService->createMainQuery($classifyResponse, $request->input('prompt'), $chatHistory);
info($queryResponse); info($queryResponse);
$firstValidation = $this->openAIService->validateSyntaxQuery($queryResponse);
$secondValidation = $this->openAIService->validateSyntaxQuery($queryResponse);
$formattedResultQuery = "[]"; $formattedResultQuery = "[]";
$queryResponse = str_replace(['```sql', '```'], '', $queryResponse); $queryResponse = str_replace(['```sql', '```'], '', $queryResponse);

View File

@@ -0,0 +1,109 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\PbgTaskAttachment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\Response;
class PbgTaskAttachmentsController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request, $pbg_task_id)
{
try{
$request->validate([
'file' => 'required|file|mimes:jpg,png,pdf|max:5120',
'pbg_type' => 'string'
]);
$attachment = PbgTaskAttachment::create([
'pbg_task_id' => $pbg_task_id,
'file_name' => $request->file('file')->getClientOriginalName(),
'file_path' => '', // empty path initially
'pbg_type' => $request->pbg_type == 'bukti_bayar' ? 'bukti_bayar' : 'berita_acara'
]);
$file = $request->file('file');
$path = $file->store("uploads/pbg-tasks/{$pbg_task_id}/{$attachment->id}", "public");
$attachment->update([
'file_path' => $path,
]);
return response()->json([
'message' => 'File uploaded successfully.',
'attachment' => [
'id' => $attachment->id,
'file_name' => $attachment->file_name,
'file_url' => Storage::url($attachment->file_path),
'pbg_type' => $attachment->pbg_type
]
]);
}catch(\Exception $e){
\Log::error($e->getMessage());
return response()->json([
"success" => false,
"message" => $e->getTraceAsString()
]);
}
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
}
public function download(string $id)
{
try {
$data = PbgTaskAttachment::findOrFail($id);
$filePath = $data->file_path; // already relative to 'public' disk
if (!Storage::disk('public')->exists($filePath)) {
return response()->json([
"success" => false,
"message" => "File not found on server"
], Response::HTTP_NOT_FOUND);
}
return Storage::disk('public')->download($filePath, $data->file_name);
} catch (\Exception $e) {
return response()->json([
"success" => false,
"message" => $e->getMessage()
]);
}
}
}

View File

@@ -21,12 +21,18 @@ class RequestAssignmentController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$query = PbgTask::query()->orderBy('id', 'desc'); $query = PbgTask::with(['attachments' => function ($q) {
if($request->has('search') && !empty($request->get("search"))){ $q->whereIn('pbg_type', ['berita_acara', 'bukti_bayar']);
$query->where('name', 'LIKE', '%'.$request->get('search').'%') }])->orderBy('id', 'desc');
->orWhere('registration_number', 'LIKE', '%'.$request->get('search').'%')
->orWhere('document_number', 'LIKE', '%'.$request->get('search').'%'); if ($request->has('search') && !empty($request->get("search"))) {
$query->where(function ($q) use ($request) {
$q->where('name', 'LIKE', '%' . $request->get('search') . '%')
->orWhere('registration_number', 'LIKE', '%' . $request->get('search') . '%')
->orWhere('document_number', 'LIKE', '%' . $request->get('search') . '%');
});
} }
return RequestAssignmentResouce::collection($query->paginate()); return RequestAssignmentResouce::collection($query->paginate());
} }

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers;
use App\Models\PbgTask;
use App\Models\PbgTaskAttachment;
use Illuminate\Http\Request;
class PbgTaskAttachmentsController extends Controller
{
public function show(string $id, Request $request){
try{
$title = $request->get('type') == "berita-acara" ? "Berita Acara" : "Bukti Bayar";
$data = PbgTaskAttachment::findOrFail($id);
$pbg = PbgTask::findOrFail($data->pbg_task_id);
return view('pbg-task-attachment.show', compact('data', 'pbg', 'title'));
}catch(\Exception $e){
return view('pages.404');
}
}
}

View File

@@ -34,6 +34,14 @@ class RequestAssignmentResouce extends JsonResource
'due_date' => $this->due_date, 'due_date' => $this->due_date,
'land_certificate_phase' => $this->land_certificate_phase, 'land_certificate_phase' => $this->land_certificate_phase,
'task_created_at' => $this->task_created_at, 'task_created_at' => $this->task_created_at,
'attachment_berita_acara' => $this->attachments
->where('pbg_type', 'berita_acara')
->sortByDesc('created_at')
->first(),
'attachment_bukti_bayar' => $this->attachments
->where('pbg_type', 'bukti_bayar')
->sortByDesc('created_at')
->first(),
]; ];
} }
} }

View File

@@ -15,6 +15,7 @@ class ImportDatasource extends Model
'response_body', 'response_body',
'status', 'status',
'start_time', 'start_time',
'finish_time' 'finish_time',
'failed_uuid'
]; ];
} }

View File

@@ -46,4 +46,8 @@ class PbgTask extends Model
{ {
return $this->hasMany(TaskAssignment::class, 'pbg_task_uid', 'uuid'); return $this->hasMany(TaskAssignment::class, 'pbg_task_uid', 'uuid');
} }
public function attachments(){
return $this->hasMany(PbgTaskAttachment::class, 'pbg_task_id', 'id');
}
} }

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PbgTaskAttachment extends Model
{
protected $fillable = ['pbg_task_id', 'file_name', 'file_path', 'pbg_type'];
public function task(){
return $this->belongsTo(PbgTask::class);
}
}

View File

@@ -27,6 +27,11 @@ class OpenAIService
return "Template prompt tidak ditemukan."; return "Template prompt tidak ditemukan.";
} }
$validationResponse = $this->validatePromptTopic($prompt);
if ($validationResponse !== 'VALID') {
return "Prompt yang Anda masukkan tidak relevan dengan data PUPR/SIMBG/DPUTR atau pekerjaan sejenis.";
}
// Ambil template berdasarkan kategori // Ambil template berdasarkan kategori
$promptTemplate = $jsonData[$mainContent]['prompt']; $promptTemplate = $jsonData[$mainContent]['prompt'];
@@ -273,4 +278,26 @@ class OpenAIService
// return trim($response['choices'][0]['message']['content'] ?? 'No response'); // return trim($response['choices'][0]['message']['content'] ?? 'No response');
// } // }
public function validatePromptTopic($prompt)
{
$messages = [
[
'role' => 'system',
'content' => "You are a classification expert. Determine if the user's request is related to the Indonesian Ministry of Public Works and Public Housing (PUPR), DPUTR, SIMBG, or construction-related tasks managed by these institutions.
Only respond with:
- VALID if it's relevant to those topics
- INVALID if not related at all"
],
['role' => 'user', 'content' => $prompt],
];
$response = $this->client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => $messages,
]);
return trim($response['choices'][0]['message']['content'] ?? 'INVALID');
}
} }

View File

@@ -108,11 +108,4 @@ return [
'database' => env('DB_CONNECTION', 'sqlite'), 'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'failed_jobs', 'table' => 'failed_jobs',
], ],
// set timeout queue
'worker' => [
'timeout' => 40000
]
]; ];

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::dropIfExists('pbg_task_attachments');
Schema::create('pbg_task_attachments', function (Blueprint $table) {
$table->id();
$table->foreignId('pbg_task_id')->constrained('pbg_task')->onDelete('cascade');
$table->string('file_name');
$table->string('file_path');
$table->string('pbg_type');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('pbg_task_attachments');
}
};

View File

@@ -15,18 +15,35 @@ class DatabaseSeeder extends Seeder
*/ */
public function run(): void public function run(): void
{ {
User::updateOrCreate( $users = [
['email' => 'user@demo.com'], // Kondisi pencarian
[ [
'name' => 'Darkone', 'email' => 'user@sibedas.com',
'name' => 'User',
'email_verified_at' => now(), 'email_verified_at' => now(),
'password' => Hash::make('password'), 'password' => Hash::make('password'),
'firstname' => 'John', 'firstname' => 'user',
'lastname' => 'Doe', 'lastname' => 'user',
'position' => 'crusial', 'position' => 'user',
'remember_token' => Str::random(10), 'remember_token' => Str::random(10),
] ],
); [
'email' => 'superadmin@sibedas.com',
'name' => 'Superadmin',
'email_verified_at' => now(),
'password' => Hash::make('password'),
'firstname' => 'superadmin',
'lastname' => 'superadmin',
'position' => 'superadmin',
'remember_token' => Str::random(10),
],
];
foreach ($users as $user) {
User::updateOrCreate(
['email' => $user['email']], // Search condition
$user // Update or create with this data
);
}
$this->call([ $this->call([
RoleSeeder::class, RoleSeeder::class,

View File

@@ -14,13 +14,20 @@ class MenuSeeder extends Seeder
*/ */
public function run(): void public function run(): void
{ {
$menus = [ $menus = [
[
"name" => "Neng Bedas",
"url" => "main-chatbot.index",
"icon" => "mingcute:wechat-line",
"parent_id" => null,
"sort_order" => 1,
],
[ [
"name" => "Dashboard", "name" => "Dashboard",
"url" => "/dashboard", "url" => "/dashboard",
"icon" => "mingcute:home-3-line", "icon" => "mingcute:home-3-line",
"parent_id" => null, "parent_id" => null,
"sort_order" => 1, "sort_order" => 2,
"children" => [ "children" => [
[ [
"name" => "Dashboard Pimpinan", "name" => "Dashboard Pimpinan",
@@ -67,7 +74,7 @@ class MenuSeeder extends Seeder
"url" => "/master", "url" => "/master",
"icon" => "mingcute:cylinder-line", "icon" => "mingcute:cylinder-line",
"parent_id" => null, "parent_id" => null,
"sort_order" => 2, "sort_order" => 3,
"children" => [ "children" => [
[ [
"name" => "Users", "name" => "Users",
@@ -82,7 +89,7 @@ class MenuSeeder extends Seeder
"url" => "/settings", "url" => "/settings",
"icon" => "mingcute:settings-6-line", "icon" => "mingcute:settings-6-line",
"parent_id" => null, "parent_id" => null,
"sort_order" => 3, "sort_order" => 4,
"children" => [ "children" => [
[ [
"name" => "Syncronize", "name" => "Syncronize",
@@ -109,7 +116,7 @@ class MenuSeeder extends Seeder
"url" => "/data-settings", "url" => "/data-settings",
"icon" => "mingcute:settings-1-line", "icon" => "mingcute:settings-1-line",
"parent_id" => null, "parent_id" => null,
"sort_order" => 4, "sort_order" => 5,
"children" => [ "children" => [
[ [
"name" => "Setting Dashboard", "name" => "Setting Dashboard",
@@ -124,7 +131,7 @@ class MenuSeeder extends Seeder
"url" => "/data", "url" => "/data",
"icon" => "mingcute:task-line", "icon" => "mingcute:task-line",
"parent_id" => null, "parent_id" => null,
"sort_order" => 5, "sort_order" => 6,
"children" => [ "children" => [
[ [
"name" => "PBG", "name" => "PBG",
@@ -187,7 +194,7 @@ class MenuSeeder extends Seeder
"url" => "/laporan", "url" => "/laporan",
"icon" => "mingcute:report-line", "icon" => "mingcute:report-line",
"parent_id" => null, "parent_id" => null,
"sort_order" => 6, "sort_order" => 7,
"children" => [ "children" => [
[ [
"name" => "Lap Pariwisata", "name" => "Lap Pariwisata",
@@ -221,21 +228,6 @@ class MenuSeeder extends Seeder
], ],
] ]
], ],
[
"name" => "Neng Bedas",
"url" => "/chat",
"icon" => "mingcute:wechat-line",
"parent_id" => null,
"sort_order" => 7,
"children" => [
[
"name" => "Chat",
"url" => "main-chatbot.index",
"icon" => null,
"sort_order" => 1,
],
]
],
[ [
"name" => "Approval", "name" => "Approval",
"url" => "/approval", "url" => "/approval",

View File

@@ -18,10 +18,6 @@ class RoleSeeder extends Seeder
"name" => "superadmin", "name" => "superadmin",
"description" => "show all menus for super admins", "description" => "show all menus for super admins",
], ],
[
"name" => "admin",
"description" => "show only necessary menus for admins",
],
[ [
"name" => "operator", "name" => "operator",
"description" => "show only necessary menus for operators", "description" => "show only necessary menus for operators",

View File

@@ -16,7 +16,7 @@ class UsersRoleMenuSeeder extends Seeder
public function run(): void public function run(): void
{ {
// Fetch roles in a single query // Fetch roles in a single query
$roles = Role::whereIn('name', ['superadmin', 'admin', 'operator'])->get()->keyBy('name'); $roles = Role::whereIn('name', ['superadmin', 'user', 'operator'])->get()->keyBy('name');
// 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', [
@@ -24,7 +24,7 @@ class UsersRoleMenuSeeder extends Seeder
'Approval', 'Tools', 'Dashboard Pimpinan', 'Dashboard PBG', 'Users', 'Syncronize', 'Approval', 'Tools', 'Dashboard Pimpinan', 'Dashboard PBG', 'Users', 'Syncronize',
'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', 'Chat', 'Dalam Sistem', 'Luar Sistem', 'Google Sheets', 'TPA TPT', 'Lap Pimpinan', 'Dalam Sistem', 'Luar Sistem', 'Google Sheets', 'TPA TPT',
'Approval Pejabat', 'Undangan', 'Rekap Pembayaran', 'Lap Rekap Data Pembayaran', 'Lap PBG (PTSP)' 'Approval Pejabat', 'Undangan', 'Rekap Pembayaran', 'Lap Rekap Data Pembayaran', 'Lap PBG (PTSP)'
])->get()->keyBy('name'); ])->get()->keyBy('name');
@@ -35,16 +35,21 @@ class UsersRoleMenuSeeder extends Seeder
'Approval', 'Tools', 'Dashboard Pimpinan', 'Dashboard PBG', 'Users', 'Syncronize', 'Approval', 'Tools', 'Dashboard Pimpinan', 'Dashboard PBG', 'Users', 'Syncronize',
'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', 'Chat', '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)' 'Undangan', 'Rekap Pembayaran', 'Lap Rekap Data Pembayaran', 'Lap PBG (PTSP)'
], ],
'admin' => ['Dashboard', 'Master', 'Settings'], 'user' => ['Dashboard', 'Data', 'Laporan', 'Neng Bedas',
'Approval', 'Tools', 'Dashboard Pimpinan', 'Dashboard PBG', 'Users', 'Syncronize',
'Menu', 'Role', 'Setting Dashboard', 'PBG', 'Reklame', 'Usaha atau Industri', 'Pariwisata',
'Lap Pariwisata', 'UMKM', 'Dashboard Potensi', 'Tata Ruang', 'PDAM', 'Dalam Sistem',
'Luar Sistem', 'Lap Pimpinan', 'Google Sheets', 'TPA TPT', 'Approval Pejabat',
'Undangan', 'Rekap Pembayaran', 'Lap Rekap Data Pembayaran', 'Lap PBG (PTSP)'],
'operator' => ['Dashboard', 'Data', 'Laporan'] 'operator' => ['Dashboard', 'Data', 'Laporan']
]; ];
// Define permission levels // Define permission levels
$superadminPermissions = ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true]; $superadminPermissions = ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true];
$adminPermissions = ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true]; $userPermissions = ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false];
$operatorPermissions = ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false]; $operatorPermissions = ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false];
// Assign menus to roles // Assign menus to roles
@@ -54,13 +59,18 @@ class UsersRoleMenuSeeder extends Seeder
$role->menus()->sync( $role->menus()->sync(
collect($menuNames)->mapWithKeys(fn($menuName) => [ collect($menuNames)->mapWithKeys(fn($menuName) => [
$menus[$menuName]->id => ($roleName === 'superadmin' ? $superadminPermissions : $menus[$menuName]->id => ($roleName === 'superadmin' ? $superadminPermissions :
($roleName === 'admin' ? $adminPermissions : $operatorPermissions)) ($roleName === 'operator' ? $operatorPermissions : $userPermissions))
])->toArray() ])->toArray()
); );
} }
} }
// Attach User to role super admin // Attach User to role super admin
User::findOrFail(1)->roles()->sync([$roles['superadmin']->id]); $accountSuperadmin = User::where('email', 'superadmin@sibedas.com')->first();
$accountUser = User::where('email', 'user@sibedas.com')->first();
$accountDefault = User::where('email','user@demo.com')->first();
$accountSuperadmin->roles()->sync([$roles['superadmin']->id]);
$accountUser->roles()->sync([$roles['user']->id]);
$accountDefault->roles()->sync([$roles['user']->id]);
} }
} }

View File

@@ -1,63 +1,124 @@
import { Grid } from "gridjs/dist/gridjs.umd.js"; import { Grid, html } from "gridjs/dist/gridjs.umd.js";
import "gridjs/dist/gridjs.umd.js";
import gridjs from "gridjs/dist/gridjs.umd.js";
import GlobalConfig from "../global-config"; import GlobalConfig from "../global-config";
import { Dropzone } from "dropzone"; import { Dropzone } from "dropzone";
Dropzone.autoDiscover = false; Dropzone.autoDiscover = false;
class PbgTasks { class PbgTasks {
constructor() {
this.table = null;
this.toastMessage = document.getElementById("toast-message");
this.toastElement = document.getElementById("toastNotification");
}
init() { init() {
this.setupFileUploadModal({
modalId: "modalBuktiBayar",
dropzoneId: "dropzoneBuktiBayar",
uploadBtnClass: "upload-btn-bukti-bayar",
removeBtnId: "removeFileBtnBuktiBayar",
submitBtnId: "submitBuktiBayar",
fileNameSpanId: "uploadedFileNameBuktiBayar",
fileInfoId: "fileInfoBuktiBayar",
pbgType: "bukti_bayar",
bindFlag: "uploadHandlerBoundBuktiBayar",
});
this.setupFileUploadModal({
modalId: "modalBeritaAcara",
dropzoneId: "dropzoneBeritaAcara",
uploadBtnClass: "upload-btn-berita-acara",
removeBtnId: "removeFileBtnBeritaAcara",
submitBtnId: "submitBeritaAcara",
fileNameSpanId: "uploadedFileNameBeritaAcara",
fileInfoId: "fileInfoBeritaAcara",
pbgType: "berita_acara",
bindFlag: "uploadHandlerBoundBeritaAcara",
});
this.initTableRequestAssignment(); this.initTableRequestAssignment();
this.handleSendNotification(); this.handleSendNotification();
this.handleUpload();
this.handleUploadBeritaAcara();
} }
initTableRequestAssignment() { initTableRequestAssignment() {
let tableContainer = document.getElementById("table-pbg-tasks"); // Ambil token
const token = document
.querySelector('meta[name="api-token"]')
.getAttribute("content");
// Pastikan kontainer kosong sebelum merender ulang Grid.js const config = {
tableContainer.innerHTML = "";
let canUpdate = tableContainer.getAttribute("data-updater") === "1";
new Grid({
columns: [ columns: [
"ID", "ID",
{ name: "Name", width: "15%" }, { name: "Name" },
{ name: "Condition", width: "7%" }, { name: "Condition" },
"Registration Number", "Registration Number",
"Document Number", "Document Number",
{ name: "Address", width: "30%" }, { name: "Address" },
"Status", "Status",
"Function Type", "Function Type",
"Consultation Type", "Consultation Type",
{ name: "Due Date", width: "10%" }, { name: "Due Date" },
{ {
name: "Action", name: "Action",
formatter: (cell) => { formatter: (cell) => {
let tableContainer =
document.getElementById("table-pbg-tasks");
let canUpdate = let canUpdate =
tableContainer.getAttribute("data-updater") === "1"; tableContainer.getAttribute("data-updater") === "1";
if (!canUpdate) { if (!canUpdate) {
return gridjs.html( return html(
`<span class="text-muted">No Privilege</span>` `<span class="text-muted">No Privilege</span>`
); );
} }
return gridjs.html(` return html(`
<div class="d-flex justify-content-center align-items-center gap-2"> <div class="d-flex justify-content-center align-items-center gap-2">
<a href="/pbg-task/${cell}" class="btn btn-yellow btn-sm d-inline-flex align-items-center justify-content-center"> <a href="/pbg-task/${cell.id}"
Detail class="btn btn-yellow btn-sm d-inline-flex align-items-center justify-content-center"
</a> style="white-space: nowrap; line-height: 1;">
<button class="btn btn-sm btn-info upload-btn" data-id="${cell}"> <iconify-icon icon="mingcute:eye-2-fill" width="15" height="15" style="vertical-align: middle;"></iconify-icon>
Upload Bukti Bayar </a>
</button>
<button class="btn btn-sm btn-info upload-btn-berita-acara" data-id="${cell}"> ${
Buat Berita Acara cell.attachment_berita_acara
</button> ? `
</div> <a href="/pbg-task-attachment/${cell.attachment_berita_acara.id}?type=berita-acara"
`); class="btn btn-success btn-sm d-inline-flex align-items-center justify-content-center"
style="white-space: nowrap; line-height: 1;"
target="_blank">
<iconify-icon icon="mingcute:eye-2-fill" width="15" height="15" style="vertical-align: middle;"></iconify-icon>
<span class="ms-1">Berita Acara</span>
</a>
`
: `
<button class="btn btn-sm btn-info d-inline-flex align-items-center justify-content-center upload-btn-berita-acara"
data-id="${cell.id}"
style="white-space: nowrap; line-height: 1;">
<iconify-icon icon="mingcute:upload-2-fill" width="15" height="15" style="vertical-align: middle;"></iconify-icon>
<span class="ms-1" style="line-height: 1;">Berita Acara</span>
</button>
`
}
${
cell.attachment_bukti_bayar
? `
<a href="/pbg-task-attachment/${cell.attachment_bukti_bayar.id}?type=bukti-bayar"
class="btn btn-success btn-sm d-inline-flex align-items-center justify-content-center"
style="white-space: nowrap; line-height: 1;"
target="_blank">
<iconify-icon icon="mingcute:eye-2-fill" width="15" height="15" style="vertical-align: middle;"></iconify-icon>
<span class="ms-1">Bukti Bayar</span>
</a>
`
: `
<button class="btn btn-sm btn-info d-inline-flex align-items-center justify-content-center upload-btn-bukti-bayar"
data-id="${cell.id}"
style="white-space: nowrap; line-height: 1;">
<iconify-icon icon="mingcute:upload-2-fill" width="15" height="15" style="vertical-align: middle;"></iconify-icon>
<span class="ms-1" style="line-height: 1;">Bukti Bayar</span>
</button>
`
}
</div>
`);
}, },
}, },
], ],
@@ -81,9 +142,7 @@ class PbgTasks {
url: `${GlobalConfig.apiHost}/api/request-assignments`, url: `${GlobalConfig.apiHost}/api/request-assignments`,
credentials: "include", credentials: "include",
headers: { headers: {
Authorization: `Bearer ${document Authorization: `Bearer ${token}`,
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
then: (data) => then: (data) =>
@@ -98,11 +157,20 @@ class PbgTasks {
item.function_type, item.function_type,
item.consultation_type, item.consultation_type,
item.due_date, item.due_date,
item.id, item,
]), ]),
total: (data) => data.meta.total, total: (data) => data.meta.total,
}, },
}).render(document.getElementById("table-pbg-tasks")); };
const tableContainer = document.getElementById("table-pbg-tasks");
if (this.table) {
this.table.updateConfig(config).forceRender();
} else {
tableContainer.innerHTML = "";
this.table = new Grid(config).render(tableContainer);
}
} }
handleSendNotification() { handleSendNotification() {
@@ -128,70 +196,208 @@ class PbgTasks {
}); });
} }
handleUpload() { setupFileUploadModal({
// Handle button click to show modal modalId,
document.addEventListener("click", function (event) { dropzoneId,
if (event.target.classList.contains("upload-btn")) { uploadBtnClass,
// Show modal removeBtnId,
let uploadModal = new bootstrap.Modal( submitBtnId,
document.getElementById("uploadModal") fileNameSpanId,
); fileInfoId,
uploadModal.show(); pbgType,
bindFlag,
}) {
const modalEl = document.getElementById(modalId);
const modalInstance = new bootstrap.Modal(modalEl);
let taskId;
// Blur-fix for modal
modalEl.addEventListener("hide.bs.modal", () => {
if (
document.activeElement &&
modalEl.contains(document.activeElement)
) {
document.activeElement.blur();
setTimeout(() => document.body.focus(), 10);
} }
}); });
let dropzone = new Dropzone("#singleFileDropzone", {
url: "/upload-bukti-bayar", // Adjust to your backend endpoint
maxFiles: 1, // Allow only 1 file
maxFilesize: 5, // Limit size to 5MB
acceptedFiles: ".jpg,.png,.pdf", // Allowed file types
autoProcessQueue: false, // Prevent automatic upload
addRemoveLinks: true, // Show remove button
dictDefaultMessage: "Drop your file here or click to upload.",
init: function () {
let dz = this;
// Remove previous file when a new file is added // Bind click listener only once
dz.on("addedfile", function () { if (!window[bindFlag]) {
if (dz.files.length > 1) { document.addEventListener("click", (e) => {
dz.removeFile(dz.files[0]); const btn = e.target.closest(`.${uploadBtnClass}`);
} if (btn) {
}); taskId = btn.getAttribute("data-id");
modalInstance.show();
}
});
window[bindFlag] = true;
}
// Handle upload button click // Avoid reinitializing Dropzone
document if (!Dropzone.instances.some((dz) => dz.element.id === dropzoneId)) {
.getElementById("uploadBtn") const self = this;
.addEventListener("click", function () {
if (dz.getQueuedFiles().length > 0) { new Dropzone(`#${dropzoneId}`, {
dz.processQueue(); // Manually process upload url: () => `/api/pbg-task-attachment/${taskId}`,
} else { maxFiles: 1,
alert("Please select a file to upload."); maxFilesize: 5, // MB
} acceptedFiles: ".jpg,.png,.pdf",
autoProcessQueue: false,
paramName: "file",
headers: {
"X-CSRF-TOKEN": document.querySelector(
'meta[name="csrf-token"]'
).content,
Authorization: `Bearer ${
document.querySelector('meta[name="api-token"]').content
}`,
Accept: "application/json",
},
params: { pbg_type: pbgType },
dictDefaultMessage: "Drop your file here or click to upload.",
init: function () {
const dz = this;
dz.on("addedfile", (file) => {
if (dz.files.length > 1) dz.removeFile(dz.files[0]);
setTimeout(() => {
document.getElementById(
fileNameSpanId
).textContent = file.name;
document
.getElementById(fileInfoId)
.classList.remove("d-none");
document
.querySelector(".dz-message")
.classList.add("d-none");
}, 10);
}); });
// Success callback dz.on("removedfile", () => {
dz.on("success", function (file, response) { document
alert("File uploaded successfully!"); .getElementById(fileInfoId)
dz.removeAllFiles(); // Clear after upload .classList.add("d-none");
}); document.getElementById(fileNameSpanId).textContent =
"";
document
.querySelector(".dz-message")
.classList.remove("d-none");
});
// Error callback document
dz.on("error", function (file, errorMessage) { .getElementById(removeBtnId)
alert("Upload failed: " + errorMessage); .addEventListener("click", () => dz.removeAllFiles());
});
}, document
}); .getElementById(submitBtnId)
.addEventListener("click", () => {
if (dz.getQueuedFiles().length > 0) {
dz.processQueue();
} else {
self.toastMessage.innerText =
"Please select a file to upload.";
self.toast.show();
}
});
dz.on("success", () => {
dz.removeAllFiles(true);
document
.getElementById(fileInfoId)
.classList.add("d-none");
document.getElementById(fileNameSpanId).textContent =
"";
document.querySelector(".dz-message").style.display =
"block";
document.activeElement.blur();
setTimeout(() => {
document.body.focus();
modalInstance.hide();
}, 50);
self.toastMessage.innerText =
"File uploaded successfully!";
self.toast.show();
self.initTableRequestAssignment();
});
dz.on("error", (file, message) => {
self.toastMessage.innerText =
message || "Upload failed!";
self.toast.show();
});
},
});
}
} }
handleUploadBeritaAcara() { handleDownloadButtons(buttonClass) {
// Handle button click to show modal const buttons = document.querySelectorAll(`.${buttonClass}`);
document.addEventListener("click", function (event) {
if (event.target.classList.contains("upload-btn-berita-acara")) { buttons.forEach((button) => {
// Show modal button.addEventListener("click", () => {
let uploadModal = new bootstrap.Modal( const attachmentId = button.getAttribute("data-id");
document.getElementById("uploadBeritaAcara") const originalContent = button.innerHTML;
);
uploadModal.show(); // Disable button & show loading
} button.disabled = true;
button.innerHTML = `
<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
Loading...
`;
fetch(`/api/pbg-task-attachment/${attachmentId}/download`, {
method: "GET",
headers: {
"X-CSRF-TOKEN": document.querySelector(
'meta[name="csrf-token"]'
).content,
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
Accept: "application/json",
},
})
.then((response) => {
if (!response.ok) {
throw new Error("File not found or server error.");
}
return response
.blob()
.then((blob) => ({ blob, response }));
})
.then(({ blob, response }) => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
const contentDisposition = response.headers.get(
"Content-Disposition"
);
let fileName = "downloaded-file";
if (contentDisposition?.includes("filename=")) {
fileName = contentDisposition
.split("filename=")[1]
.replace(/"/g, "")
.trim();
}
a.download = fileName;
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
})
.catch((error) => {
console.error("Download failed:", error);
alert("Failed to download file.");
})
.finally(() => {
button.disabled = false;
button.innerHTML = originalContent;
});
});
}); });
} }
} }

View File

@@ -31,7 +31,10 @@ class SyncronizeTask {
{ {
name: "Action", name: "Action",
formatter: (cell) => { formatter: (cell) => {
if (cell.status === "failed") { if (
cell.status === "failed" &&
cell.failed_uuid !== null
) {
return gridjs.html(` return gridjs.html(`
<button data-id="${cell.id}" class="btn btn-sm btn-warning d-flex align-items-center gap-1 btn-retry"> <button data-id="${cell.id}" class="btn btn-sm btn-warning d-flex align-items-center gap-1 btn-retry">
<iconify-icon icon="mingcute:refresh-3-line" width="15" height="15"></iconify-icon> <iconify-icon icon="mingcute:refresh-3-line" width="15" height="15"></iconify-icon>

View File

@@ -0,0 +1,50 @@
@extends('layouts.vertical', ['subtitle' => $title])
@section('content')
@include('layouts.partials.page-title', ['title' => 'Data', 'subtitle' => 'PBG'])
<div class="row mb-4">
<div class="col-sm-12">
<div class="card border shadow-sm">
<div class="card-body">
<h5 class="mb-3">{{ $title }}</h5>
<p><strong>Document Number:</strong> {{ $pbg->document_number }}</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="card border shadow-sm">
<div class="card-body">
@php
$extension = strtolower(pathinfo($data->file_name, PATHINFO_EXTENSION));
@endphp
@if (in_array($extension, ['jpg', 'jpeg', 'png']))
<div class="text-center">
<img
src="{{ asset('storage/' . $data->file_path) }}"
alt="{{ $data->file_name }}"
class="img-fluid border rounded"
style="max-height: 600px;"
>
</div>
@elseif ($extension === 'pdf')
<iframe
src="{{ asset('storage/' . $data->file_path) }}"
width="100%"
height="700px"
style="border: none;"
></iframe>
@else
<div class="alert alert-warning">
Unsupported file type: <strong>{{ $extension }}</strong>
</div>
@endif
</div>
</div>
</div>
</div>
@endsection

View File

@@ -2,6 +2,26 @@
@section('css') @section('css')
@vite(['node_modules/gridjs/dist/theme/mermaid.min.css']) @vite(['node_modules/gridjs/dist/theme/mermaid.min.css'])
<style>
#dropzoneBuktiBayar .dz-preview{
display: none;
}
#dropzoneBeritaAcara .dz-preview{
display: none;
}
.file-info-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
background: rgba(255, 255, 255, 0.9);
padding: 0.75rem 1rem;
border-radius: 0.5rem;
display: flex;
align-items: center;
}
</style>
@endsection @endsection
@section('content') @section('content')
@@ -71,7 +91,7 @@
</div> </div>
<!-- Modal --> <!-- Modal -->
<div class="modal fade" id="uploadModal" tabindex="-1" aria-hidden="true"> <div class="modal fade" id="modalBuktiBayar" tabindex="-1" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@@ -80,20 +100,25 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="mb-3"> <div class="mb-3">
<form action="/upload-bukti-bayar" method="POST" class="dropzone" id="singleFileDropzone"> <form action="/upload-bukti-bayar" method="POST" class="dropzone" id="dropzoneBuktiBayar">
<div class="dz-message needsclick"> <div class="dz-message needsclick">
<i class="h1 bx bx-cloud-upload"></i> <i class="h1 bx bx-cloud-upload"></i>
<h3>Drop file here or click to upload.</h3> <h3>Drop file here or click to upload.</h3>
<span class="text-muted fs-13"> <span class="text-muted fs-13">
(Only one file allowed. Selected file will be uploaded upon clicking submit.) (Only one file allowed. Selected file will be uploaded upon clicking submit.)
</span> </span>
</div> </div>
<!-- File info inside dropzone -->
<div id="fileInfoBuktiBayar" class="file-info-overlay d-none">
<span id="uploadedFileNameBuktiBayar" class="text-muted me-3"></span>
<button type="button" id="removeFileBtnBuktiBayar" class="btn btn-sm btn-danger">Hapus</button>
</div>
</form> </form>
</div> </div>
<!-- Submit Button --> <!-- Submit Button -->
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<button type="button" id="uploadBtn" class="btn btn-success"> <button type="button" id="submitBuktiBayar" class="btn btn-success">
<i class="bx bx-upload"></i> Upload <i class="bx bx-upload"></i> Upload
</button> </button>
</div> </div>
@@ -103,31 +128,36 @@
</div> </div>
<!-- Modal --> <!-- Modal -->
<div class="modal fade" id="uploadBeritaAcara" tabindex="-1" aria-hidden="true"> <div class="modal fade" id="modalBeritaAcara" tabindex="-1" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Upload Berita Acara</h5> <h5 class="modal-title">Upload Berita Acara</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="mb-3"> <div class="mb-3">
<form action="/upload-berita-acara" method="POST" class="dropzone" id="singleFileDropzone"> <form action="/upload-berita-acara" method="POST" class="dropzone" id="dropzoneBeritaAcara">
<div class="dz-message needsclick"> <div class="dz-message needsclick">
<i class="h1 bx bx-cloud-upload"></i> <i class="h1 bx bx-cloud-upload"></i>
<h3>Drop file here or click to upload.</h3> <h3>Drop file here or click to upload.</h3>
<span class="text-muted fs-13"> <span class="text-muted fs-13">
(Only one file allowed. Selected file will be uploaded upon clicking submit.) (Only one file allowed. Selected file will be uploaded upon clicking submit.)
</span> </span>
</div> </div>
</form> <!-- File info inside dropzone -->
<div id="fileInfoBeritaAcara" class="file-info-overlay d-none">
<span id="uploadedFileNameBeritaAcara" class="text-muted me-3"></span>
<button type="button" id="removeFileBtnBeritaAcara" class="btn btn-sm btn-danger">Hapus</button>
</div>
</form>
</div> </div>
<!-- Submit Button --> <!-- Submit Button -->
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<button type="button" id="uploadBeritaAcara" class="btn btn-success"> <button type="button" id="submitBeritaAcara" class="btn btn-success">
<i class="bx bx-upload"></i> Upload <i class="bx bx-upload"></i> Upload
</button> </button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -10,6 +10,7 @@ use App\Http\Controllers\Api\GoogleSheetController;
use App\Http\Controllers\Api\ImportDatasourceController; use App\Http\Controllers\Api\ImportDatasourceController;
use App\Http\Controllers\Api\LackOfPotentialController; use App\Http\Controllers\Api\LackOfPotentialController;
use App\Http\Controllers\Api\MenusController; use App\Http\Controllers\Api\MenusController;
use App\Http\Controllers\Api\PbgTaskAttachmentsController;
use App\Http\Controllers\Api\PbgTaskController; use App\Http\Controllers\Api\PbgTaskController;
use App\Http\Controllers\Api\PbgTaskGoogleSheetsController; use App\Http\Controllers\Api\PbgTaskGoogleSheetsController;
use App\Http\Controllers\Api\ReportPbgPtspController; use App\Http\Controllers\Api\ReportPbgPtspController;
@@ -181,4 +182,9 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
Route::get('/report-ptsp/excel', 'export_excel')->name('api.report-ptsp.excel'); Route::get('/report-ptsp/excel', 'export_excel')->name('api.report-ptsp.excel');
Route::get('/report-ptsp/pdf', 'export_pdf')->name('api.report-ptsp.pdf'); Route::get('/report-ptsp/pdf', 'export_pdf')->name('api.report-ptsp.pdf');
}); });
Route::controller(PbgTaskAttachmentsController::class)->group(function (){
Route::post('/pbg-task-attachment/{pbg_task_id}', 'store')->name('api.pbg-task.upload');
Route::get('/pbg-task-attachment/{attachment_id}/download', 'download')->name('api.pbg-task.download');
});
}); });

View File

@@ -14,6 +14,7 @@ use App\Http\Controllers\InvitationsController;
use App\Http\Controllers\Master\UsersController; use App\Http\Controllers\Master\UsersController;
use App\Http\Controllers\MenusController; use App\Http\Controllers\MenusController;
use App\Http\Controllers\PaymentRecapsController; use App\Http\Controllers\PaymentRecapsController;
use App\Http\Controllers\PbgTaskAttachmentsController;
use App\Http\Controllers\ReportPaymentRecapsController; use App\Http\Controllers\ReportPaymentRecapsController;
use App\Http\Controllers\ReportPbgPTSPController; use App\Http\Controllers\ReportPbgPTSPController;
use App\Http\Controllers\RequestAssignment\PbgTaskController; use App\Http\Controllers\RequestAssignment\PbgTaskController;
@@ -64,6 +65,7 @@ Route::group(['middleware' => 'auth'], function(){
// data - PBG // data - PBG
Route::resource('/pbg-task', PbgTaskController::class); Route::resource('/pbg-task', PbgTaskController::class);
Route::get('/pbg-task-attachment/{attachment_id}', [PbgTaskAttachmentsController::class, 'show'])->name('pbg-task-attachment.show');
// data settings // data settings
Route::resource('/data-settings', DataSettingController::class); Route::resource('/data-settings', DataSettingController::class);

View File

@@ -81,7 +81,7 @@ CREATE TABLE `bigdata_resumes` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `bigdata_resumes_import_datasource_id_foreign` (`import_datasource_id`), KEY `bigdata_resumes_import_datasource_id_foreign` (`import_datasource_id`),
CONSTRAINT `bigdata_resumes_import_datasource_id_foreign` FOREIGN KEY (`import_datasource_id`) REFERENCES `import_datasources` (`id`) ON DELETE CASCADE CONSTRAINT `bigdata_resumes_import_datasource_id_foreign` FOREIGN KEY (`import_datasource_id`) REFERENCES `import_datasources` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -266,7 +266,7 @@ CREATE TABLE `failed_jobs` (
`failed_at` timestamp NOT NULL DEFAULT current_timestamp(), `failed_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`) UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -301,12 +301,13 @@ CREATE TABLE `import_datasources` (
`message` varchar(255) DEFAULT NULL, `message` varchar(255) DEFAULT NULL,
`response_body` text DEFAULT NULL, `response_body` text DEFAULT NULL,
`status` varchar(255) NOT NULL DEFAULT 'processing', `status` varchar(255) NOT NULL DEFAULT 'processing',
`failed_uuid` varchar(255) DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
`start_time` timestamp NULL DEFAULT NULL, `start_time` timestamp NULL DEFAULT NULL,
`finish_time` timestamp NULL DEFAULT NULL, `finish_time` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=113 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=116 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -348,7 +349,7 @@ CREATE TABLE `jobs` (
`created_at` int(10) unsigned NOT NULL, `created_at` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `jobs_queue_index` (`queue`) KEY `jobs_queue_index` (`queue`)
) ENGINE=InnoDB AUTO_INCREMENT=1180 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=1189 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -385,7 +386,7 @@ CREATE TABLE `migrations` (
`migration` varchar(255) NOT NULL, `migration` varchar(255) NOT NULL,
`batch` int(11) NOT NULL, `batch` int(11) NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=85 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -434,7 +435,7 @@ CREATE TABLE `pbg_task` (
`task_created_at` timestamp NULL DEFAULT NULL, `task_created_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `pbg_task_uuid_unique` (`uuid`) UNIQUE KEY `pbg_task_uuid_unique` (`uuid`)
) ENGINE=InnoDB AUTO_INCREMENT=357401 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=380902 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -504,7 +505,7 @@ CREATE TABLE `pbg_task_google_sheet` (
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `pbg_task_google_sheet_no_registrasi_unique` (`no_registrasi`) UNIQUE KEY `pbg_task_google_sheet_no_registrasi_unique` (`no_registrasi`)
) ENGINE=InnoDB AUTO_INCREMENT=232043 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=244256 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -528,7 +529,7 @@ CREATE TABLE `pbg_task_index_integrations` (
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `pbg_task_index_integrations_pbg_task_uid_unique` (`pbg_task_uid`) UNIQUE KEY `pbg_task_index_integrations_pbg_task_uid_unique` (`pbg_task_uid`)
) ENGINE=InnoDB AUTO_INCREMENT=50738 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=50759 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -630,7 +631,7 @@ CREATE TABLE `personal_access_tokens` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `personal_access_tokens_token_unique` (`token`), UNIQUE KEY `personal_access_tokens_token_unique` (`token`),
KEY `personal_access_tokens_tokenable_type_tokenable_id_index` (`tokenable_type`,`tokenable_id`) KEY `personal_access_tokens_tokenable_type_tokenable_id_index` (`tokenable_type`,`tokenable_id`)
) ENGINE=InnoDB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -706,7 +707,7 @@ CREATE TABLE `roles` (
`updated_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `roles_name_unique` (`name`) UNIQUE KEY `roles_name_unique` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=73 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=74 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -784,7 +785,7 @@ CREATE TABLE `task_assignments` (
UNIQUE KEY `task_assignments_uid_unique` (`uid`), UNIQUE KEY `task_assignments_uid_unique` (`uid`),
KEY `task_assignments_pbg_task_uid_foreign` (`pbg_task_uid`), KEY `task_assignments_pbg_task_uid_foreign` (`pbg_task_uid`),
CONSTRAINT `task_assignments_pbg_task_uid_foreign` FOREIGN KEY (`pbg_task_uid`) REFERENCES `pbg_task` (`uuid`) ON DELETE CASCADE CONSTRAINT `task_assignments_pbg_task_uid_foreign` FOREIGN KEY (`pbg_task_uid`) REFERENCES `pbg_task` (`uuid`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=8476 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB AUTO_INCREMENT=8492 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@@ -1160,4 +1161,4 @@ CREATE TABLE `villages` (
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2025-03-24 10:33:36 -- Dump completed on 2025-03-27 15:26:56