diff --git a/README.md b/README.md index 1a4c26b..7caa749 100755 --- a/README.md +++ b/README.md @@ -1,66 +1,37 @@ -
+# Usage icon - +search or pick icon in here -## About Laravel +# Set up queue for running automatically -Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: +- Install Supervisor -- [Simple, fast routing engine](https://laravel.com/docs/routing). -- [Powerful dependency injection container](https://laravel.com/docs/container). -- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. -- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). -- Database agnostic [schema migrations](https://laravel.com/docs/migrations). -- [Robust background job processing](https://laravel.com/docs/queues). -- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). +``` +sudo apt update && sudo apt install supervisor -y +``` -Laravel is accessible, powerful, and provides tools required for large, robust applications. +- Create Supervisor Config -## Learning Laravel +``` +sudo nano /etc/supervisor/conf.d/laravel-worker.conf -Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. +[program:laravel-worker] +process_name=%(program_name)s_%(process_num)02d +command=php /path-to-your-project/artisan queue:work --tries=3 --timeout=600 +autostart=true +autorestart=true +numprocs=1 +user=www-data +redirect_stderr=true +stdout_logfile=/var/log/supervisor/laravel-worker.log +``` -You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. +- Reload Supervisor -If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. - -## Laravel Sponsors - -We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). - -### Premium Partners - -- **[Vehikl](https://vehikl.com/)** -- **[Tighten Co.](https://tighten.co)** -- **[WebReinvent](https://webreinvent.com/)** -- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** -- **[64 Robots](https://64robots.com)** -- **[Curotec](https://www.curotec.com/services/technologies/laravel/)** -- **[Cyber-Duck](https://cyber-duck.co.uk)** -- **[DevSquad](https://devsquad.com/hire-laravel-developers)** -- **[Jump24](https://jump24.co.uk)** -- **[Redberry](https://redberry.international/laravel/)** -- **[Active Logic](https://activelogic.com)** -- **[byte5](https://byte5.de)** -- **[OP.GG](https://op.gg)** - -## Contributing - -Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). - -## Code of Conduct - -In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). - -## Security Vulnerabilities - -If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. - -## License - -The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). +``` +sudo supervisorctl reread +sudo supervisorctl update +sudo supervisorctl start laravel-worker +sudo supervisorctl restart laravel-worker +sudo supervisorctl status +``` diff --git a/app/Http/Controllers/Api/BigDataResumeController.php b/app/Http/Controllers/Api/BigDataResumeController.php index e986c12..f5f9991 100644 --- a/app/Http/Controllers/Api/BigDataResumeController.php +++ b/app/Http/Controllers/Api/BigDataResumeController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; +use App\Http\Resources\BigdataResumeResource; use App\Models\BigdataResume; use App\Models\DataSetting; use Illuminate\Http\Request; @@ -149,6 +150,22 @@ class BigDataResumeController extends Controller } } + public function bigdata_report(Request $request){ + try{ + $query = BigdataResume::query()->orderBy('id', 'desc'); + + if($request->filled('search')){ + $query->where('name', 'LIKE', '%'.$request->input('search').'%'); + } + + $query = $query->paginate(15); + return BigdataResumeResource::collection($query)->response()->getData(true); + }catch(\Exception $e){ + Log::error($e->getMessage()); + return response()->json(['message' => 'Error when fetching data'], 500); + } + } + /** * Store a newly created resource in storage. */ diff --git a/app/Http/Controllers/Api/CustomersController.php b/app/Http/Controllers/Api/CustomersController.php index d759a8d..33b5d44 100644 --- a/app/Http/Controllers/Api/CustomersController.php +++ b/app/Http/Controllers/Api/CustomersController.php @@ -22,7 +22,7 @@ class CustomersController extends Controller if ($request->has('search') &&!empty($request->get('search'))) { $query = $query->where('nomor_pelanggan', 'LIKE', '%'.$request->get('search').'%') ->orWhere('nama', 'LIKE', '%'.$request->get('search').'%') - ->orWhere('kota_palayanan', 'LIKE', '%'.$request->get('search').'%'); + ->orWhere('kota_pelayanan', 'LIKE', '%'.$request->get('search').'%'); } return CustomersResource::collection($query->paginate()); } diff --git a/app/Http/Controllers/BigdataResumesController.php b/app/Http/Controllers/BigdataResumesController.php new file mode 100644 index 0000000..e8b5fb9 --- /dev/null +++ b/app/Http/Controllers/BigdataResumesController.php @@ -0,0 +1,48 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'import_datasource_id' => $this->import_datasource_id, + 'potention_count' => (int) $this->potention_count, + 'potention_sum' => number_format((float) $this->potention_sum, 2, ',', '.'), + + 'non_verified_count' => (int) $this->non_verified_count, + 'non_verified_sum' => number_format((float) $this->non_verified_sum, 2, ',', '.'), + + 'verified_count' => (int) $this->verified_count, + 'verified_sum' => number_format((float) $this->verified_sum, 2, ',', '.'), + + 'business_count' => (int) $this->business_count, + 'business_sum' => number_format((float) $this->business_sum, 2, ',', '.'), + + 'non_business_count' => (int) $this->non_business_count, + 'non_business_sum' => number_format((float) $this->non_business_sum, 2, ',', '.'), + + 'spatial_count' => (int) $this->spatial_count, + 'spatial_sum' => number_format((float) $this->spatial_sum, 2, ',', '.'), + + 'year' => $this->year, + 'created_at' => $this->created_at->toDateTimeString(), + ]; + } +} diff --git a/app/Imports/CustomersImport.php b/app/Imports/CustomersImport.php index e448faa..dff0824 100644 --- a/app/Imports/CustomersImport.php +++ b/app/Imports/CustomersImport.php @@ -11,42 +11,87 @@ use Maatwebsite\Excel\Concerns\WithHeadingRow; use Maatwebsite\Excel\Concerns\WithMultipleSheets; use Illuminate\Contracts\Queue\ShouldQueue; use Maatwebsite\Excel\Concerns\WithBatchInserts; +use Illuminate\Support\Facades\Log; -class CustomersImport implements ToCollection, WithMultipleSheets +class CustomersImport implements ToCollection, WithMultipleSheets, WithChunkReading, WithBatchInserts, ShouldQueue, WithHeadingRow { /** * @param Collection $collection - */ + */ public function collection(Collection $collection) { $batchData = []; + $batchSize = 1000; // Process in smaller chunks - foreach ($collection->skip(1) as $row) { - if (!isset($row[0]) || empty($row[0])) { - continue; + foreach ($collection as $row) { + if (!isset($row['nomor_pelanggan']) || empty($row['nomor_pelanggan'])) { + continue; // Skip rows without 'nomor_pelanggan' } - $latitude = filter_var($row[4], FILTER_VALIDATE_FLOAT) ? bcadd($row[4], '0', 18) : null; - $longitude = filter_var($row[5], FILTER_VALIDATE_FLOAT) ? bcadd($row[5], '0', 18) : null; + // Default values + $latitude = '0'; + $longitude = '0'; + + // Convert and normalize latitude + if (isset($row['latkor']) && !empty(trim($row['latkor']))) { + $latitude = str_replace(',', '.', trim($row['latkor'])); // Replace comma with dot + if (is_numeric($latitude)) { + $latitude = bcadd($latitude, '0', 18); // Convert to decimal with 18 precision + } else { + $latitude = '0'; // Default fallback + } + } else { + $latitude = '0'; + } + + // Convert and normalize longitude + if (isset($row['lonkor']) && !empty(trim($row['lonkor']))) { + $longitude = str_replace(',', '.', trim($row['lonkor'])); // Replace comma with dot + if (is_numeric($longitude)) { + $longitude = bcadd($longitude, '0', 18); // Convert to decimal with 18 precision + } else { + $longitude = '0'; // Default fallback + } + } else { + $longitude = '0'; + } $batchData[] = [ - 'nomor_pelanggan' => $row[0], - 'kota_pelayanan' => $row[1], - 'nama' => $row[2], - 'alamat' => $row[3], + 'nomor_pelanggan' => $row['nomor_pelanggan'] ?? '', + 'kota_pelayanan' => $row['kota_pelayanan'] ?? '', + 'nama' => $row['nama'] ?? '', + 'alamat' => $row['alamat'] ?? '', 'latitude' => $latitude, 'longitude' => $longitude, ]; + + // Batch insert every 1000 rows + if (count($batchData) >= $batchSize) { + Customer::upsert($batchData, ['nomor_pelanggan'], ['kota_pelayanan', 'nama', 'alamat', 'latitude', 'longitude']); + $batchData = []; // Clear the batch + } } + // Insert remaining data if (!empty($batchData)) { Customer::upsert($batchData, ['nomor_pelanggan'], ['kota_pelayanan', 'nama', 'alamat', 'latitude', 'longitude']); } } + public function sheets(): array { return [ 0 => $this ]; } + + public function chunkSize(): int + { + return 1000; + } + + public function batchSize(): int + { + return 1000; + } } diff --git a/composer.json b/composer.json index a1bcd80..6af873f 100755 --- a/composer.json +++ b/composer.json @@ -56,7 +56,7 @@ ], "dev": [ "Composer\\Config::disableProcessTimeout", - "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:work --timeout=300 --tries=3\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite" + "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite" ] }, "extra": { diff --git a/database/seeders/UsersRoleMenuSeeder.php b/database/seeders/UsersRoleMenuSeeder.php index b405014..9e4667d 100644 --- a/database/seeders/UsersRoleMenuSeeder.php +++ b/database/seeders/UsersRoleMenuSeeder.php @@ -71,7 +71,7 @@ class UsersRoleMenuSeeder extends Seeder [ "name" => "Laporan", "url" => "/laporan", - "icon" => "mingcute:task-line", + "icon" => "mingcute:report-line", "parent_id" => null, "sort_order" => 6, ] @@ -214,6 +214,13 @@ class UsersRoleMenuSeeder extends Seeder "parent_id" => $laporan->id, "sort_order" => 1, ], + [ + "name" => "Lap Pimpinan", + "url" => "bigdata-resumes", + "icon" => null, + "parent_id" => $laporan->id, + "sort_order" => 2, + ], ]; foreach ($children_menus as $child_menu) { @@ -237,6 +244,7 @@ class UsersRoleMenuSeeder extends Seeder $spatial_plannings = Menu::where('name', 'Tata Ruang')->first(); $pdam = Menu::where('name', 'PDAM')->first(); $peta = Menu::where('name', 'PETA')->first(); + $bigdata_resume = Menu::where('name', 'Lap Pimpinan')->first(); // Superadmin gets all menus $superadmin->menus()->sync([ @@ -265,6 +273,7 @@ class UsersRoleMenuSeeder extends Seeder $spatial_plannings->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $pdam->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $peta->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], + $bigdata_resume->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], ]); // Admin gets limited menus @@ -279,6 +288,7 @@ class UsersRoleMenuSeeder extends Seeder $dashboard->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $data->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], ]); + // Attach User to role super admin User::findOrFail(1)->roles()->sync([$superadmin->id]); } diff --git a/resources/js/bigdata-resumes/index.js b/resources/js/bigdata-resumes/index.js new file mode 100644 index 0000000..a984601 --- /dev/null +++ b/resources/js/bigdata-resumes/index.js @@ -0,0 +1,154 @@ +import { Grid } from "gridjs/dist/gridjs.umd.js"; +import gridjs from "gridjs/dist/gridjs.umd.js"; +import "gridjs/dist/gridjs.umd.js"; +import GlobalConfig, { addThousandSeparators } from "../global-config.js"; +import Swal from "sweetalert2"; +import moment from "moment"; + +class BigdataResume { + constructor() { + this.toastMessage = document.getElementById("toast-message"); + this.toastElement = document.getElementById("toastNotification"); + this.toast = new bootstrap.Toast(this.toastElement); + this.table = null; + + // Initialize functions + this.initTableDataSettings(); + // this.initEvents(); + } + initEvents() { + document.body.addEventListener("click", async (event) => { + const deleteButton = event.target.closest( + ".btn-delete-data-settings" + ); + if (deleteButton) { + event.preventDefault(); + await this.handleDelete(deleteButton); + } + }); + } + + initTableDataSettings() { + let tableContainer = document.getElementById("table-bigdata-resumes"); + // Create a new Grid.js instance only if it doesn't exist + this.table = new Grid({ + columns: [ + { name: "ID" }, + { name: "Potention Count" }, + { name: "Potention Sum" }, + { name: "Non Verified Count" }, + { name: "Non Verified Sum" }, + { name: "Verified Count" }, + { name: "Verified Sum" }, + { name: "Business Count" }, + { name: "Business Sum" }, + { name: "Non Business Count" }, + { name: "Non Business Sum" }, + { name: "Spatial Sum" }, + { name: "Spatial Count" }, + { + name: "Created", + attributes: { style: "width: 200px; white-space: nowrap;" }, // Set width dynamically + }, + ], + pagination: { + limit: 15, + server: { + url: (prev, page) => + `${prev}${prev.includes("?") ? "&" : "?"}page=${ + page + 1 + }`, + }, + }, + sort: true, + search: { + server: { + url: (prev, keyword) => `${prev}?search=${keyword}`, + }, + }, + server: { + url: `${GlobalConfig.apiHost}/api/bigdata-report`, + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + "Content-Type": "application/json", + }, + then: (data) => + data.data.map((item) => [ + item.id, + item.potention_count, + addThousandSeparators(item.potention_sum), + item.non_verified_count, + addThousandSeparators(item.non_verified_sum), + item.verified_count, + addThousandSeparators(item.verified_sum), + item.business_count, + addThousandSeparators(item.business_sum), + item.non_business_count, + addThousandSeparators(item.non_business_sum), + item.spatial_count, + addThousandSeparators(item.spatial_sum), + moment(item.created_at).format("YYYY-MM-DD H:mm:ss"), + ]), + total: (data) => data.total, + }, + }).render(tableContainer); + } + async handleDelete(deleteButton) { + const id = deleteButton.getAttribute("data-id"); + + const result = await Swal.fire({ + title: "Are you sure?", + text: "You won't be able to revert this!", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!", + }); + + if (result.isConfirmed) { + try { + let response = await fetch( + `${GlobalConfig.apiHost}/api/data-settings/${id}`, + { + method: "DELETE", + credentials: "include", + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + "Content-Type": "application/json", + }, + } + ); + + if (response.ok) { + let result = await response.json(); + this.toastMessage.innerText = + result.message || "Deleted successfully!"; + this.toast.show(); + + // Refresh Grid.js table + if (typeof this.table !== "undefined") { + this.table.updateConfig({}).forceRender(); + } + } else { + let error = await response.json(); + console.error("Delete failed:", error); + this.toastMessage.innerText = + error.message || "Delete failed!"; + this.toast.show(); + } + } catch (error) { + console.error("Error deleting item:", error); + this.toastMessage.innerText = "An error occurred!"; + this.toast.show(); + } + } + } +} +document.addEventListener("DOMContentLoaded", function (e) { + new BigdataResume(); +}); diff --git a/resources/views/bigdata-resumes/index.blade.php b/resources/views/bigdata-resumes/index.blade.php new file mode 100644 index 0000000..9a23c18 --- /dev/null +++ b/resources/views/bigdata-resumes/index.blade.php @@ -0,0 +1,27 @@ +@extends('layouts.vertical', ['subtitle' => 'Laporan Pimpinan']) + +@section('css') +@vite(['node_modules/gridjs/dist/theme/mermaid.min.css']) +@endsection + +@section('content') + +@include('layouts.partials/page-title', ['title' => 'Laporan', 'subtitle' => 'Laporan Pimpinan']) + +