diff --git a/app/Http/Controllers/Api/BusinessOrIndustriesController.php b/app/Http/Controllers/Api/BusinessOrIndustriesController.php new file mode 100644 index 0000000..dc6eb57 --- /dev/null +++ b/app/Http/Controllers/Api/BusinessOrIndustriesController.php @@ -0,0 +1,120 @@ +orderBy('id', 'desc'); + + if ($request->has("search") && !empty($request->get("search"))) { + $search = $request->get("search"); + + info($request); // Debugging log + + $query->where(function ($q) use ($search) { + $q->where("nop", "LIKE", "%{$search}%") + ->orWhere("nama_kecamatan", "LIKE", "%{$search}%") + ->orWhere("nama_kelurahan", "LIKE", "%{$search}%"); + }); + } + + return response()->json($query->paginate()); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + // + } + + /** + * Display the specified resource. + */ + public function show(string $id) + { + // + } + + /** + * Update the specified resource in storage. + */ + public function update(BusinessIndustryRequest $request, string $id) + { + try{ + $data = BusinessOrIndustry::findOrFail($id); + $data->update($request->validated()); + return response()->json(['message' => 'Data updated successfully.'], 200); + }catch(\Exception $e){ + \Log::error($e->getMessage()); + return response()->json(['message' => 'Failed to update data'],500); + } + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(string $id) + { + try{ + $data = BusinessOrIndustry::findOrFail($id); + $data->delete(); + return response()->json(['message' => 'Data deleted successfully.'], 200); + }catch(\Exception $e){ + \Log::error($e->getMessage()); + return response()->json(['message' => 'Failed to delete data'],500); + } + } + + public function upload(Request $request){ + + if ($request->hasFile('file')) { + $file = $request->file('file'); + } + + // Validasi file + $validator = Validator::make($request->all(), [ + 'file' => 'required|mimes:xlsx,xls|max:102400', // Max 100MB + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'File validation failed.', + 'errors' => $validator->errors() + ], 400); + } + + try { + // Ambil file dari request + $file = $request->file('file'); + + // Menggunakan Laravel Excel untuk mengimpor file + Excel::import(new BusinessIndustriesImport, $file); + + // Jika sukses, kembalikan respons sukses + return response()->json([ + 'message' => 'File uploaded and imported successfully!' + ], 200); + } catch (\Exception $e) { + // Jika ada error, kembalikan error response + return response()->json([ + 'message' => 'Error during file import.', + 'error' => $e->getMessage() + ], 500); + } + + } +} diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 2985421..ce9a390 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -4,9 +4,11 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Http\Requests\Auth\LoginRequest; +use App\Http\Requests\UsersRequest; use App\Http\Resources\UserResource; use App\Models\User; use App\Traits\GlobalApiResponse; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; use Illuminate\Http\Request; @@ -35,4 +37,27 @@ class UsersController extends Controller $request->user()->tokens()->delete(); return response()->json(['message' => 'logged out successfully']); } + public function store(UsersRequest $request){ + $validate_data = $request->validated(); + + DB::beginTransaction(); + try{ + $user = User::create([ + 'name' => $validate_data->name, + 'email' => $validate_data->email, + 'password' => Hash::make($validate_data->password), + 'firstname' => $validate_data->firstname, + 'lastname' => $validate_data->lastname, + 'position' => $validate_data->position + ]); + + $user->roles()->attach($request->role_id); + + DB::commit(); + return response()->json(['message' => 'Successfully created'],201); + }catch(\Exception $e){ + DB::rollBack(); + return response()->json(['message' => $e->getMessage()],500); + }; + } } diff --git a/app/Http/Controllers/BusinessOrIndustriesController.php b/app/Http/Controllers/BusinessOrIndustriesController.php new file mode 100644 index 0000000..b2d5b92 --- /dev/null +++ b/app/Http/Controllers/BusinessOrIndustriesController.php @@ -0,0 +1,66 @@ +validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], @@ -39,6 +40,7 @@ class UsersController extends Controller 'role_id' => 'required|exists:roles,id' ]); + DB::beginTransaction(); try{ $user = User::create([ diff --git a/app/Http/Requests/BusinessIndustryRequest.php b/app/Http/Requests/BusinessIndustryRequest.php new file mode 100644 index 0000000..5f3af3c --- /dev/null +++ b/app/Http/Requests/BusinessIndustryRequest.php @@ -0,0 +1,39 @@ +|string> + */ + public function rules(): array + { + return [ + 'nama_kecamatan' => 'required|string|max:255', + 'nama_kelurahan' => 'required|string|max:255', + 'nop' => 'required|string|max:255|unique:business_or_industries,nop,' . $this->route('api_business_industry'), + 'nama_wajib_pajak' => 'required|string|max:255', + 'alamat_wajib_pajak' => 'nullable|string|max:255', + 'alamat_objek_pajak' => 'required|string|max:255', + 'luas_bumi' => 'required|numeric', + 'luas_bangunan' => 'required|numeric', + 'njop_bumi' => 'required|numeric', + 'njop_bangunan' => 'required|numeric', + 'ketetapan' => 'required|string|max:255', + 'tahun_pajak' => 'required|integer|min:1900|max:' . date('Y'), + ]; + } +} diff --git a/app/Http/Requests/UsersRequest.php b/app/Http/Requests/UsersRequest.php new file mode 100644 index 0000000..95ffd95 --- /dev/null +++ b/app/Http/Requests/UsersRequest.php @@ -0,0 +1,43 @@ +|string> + */ + public function rules(): array + { + $userId = $this->route('user'); // Get user ID from route (used in update) + + return [ + 'name' => ['required', 'string', 'max:255'], + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + Rule::unique('users')->ignore($userId), // Ignore the user's own email when updating + ], + 'password' => [$this->isMethod('post') ? 'required' : 'nullable', 'confirmed', 'max:255'], + 'firstname' => ['required', 'string', 'max:255'], + 'lastname' => ['required', 'string', 'max:255'], + 'position' => ['required', 'string', 'max:255'], + 'role_id' => ['required', 'exists:roles,id'], + ]; + } +} diff --git a/app/Imports/BusinessIndustriesImport.php b/app/Imports/BusinessIndustriesImport.php new file mode 100644 index 0000000..3cdc88d --- /dev/null +++ b/app/Imports/BusinessIndustriesImport.php @@ -0,0 +1,39 @@ +skip(1) as $row){ + $clean_nop = preg_replace('/[^A-Za-z0-9]/', '', $row[2]); + if (!BusinessOrIndustry::where('nop', $clean_nop)->exists()) { + BusinessOrIndustry::create([ + 'nama_kecamatan' => $row[0], + 'nama_kelurahan' => $row[1], + 'nop' => $clean_nop, // Store cleaned 'nop' + 'nama_wajib_pajak' => $row[3], + 'alamat_wajib_pajak' => $row[4], + 'alamat_objek_pajak' => $row[5], + 'luas_bumi' => $row[6], + 'luas_bangunan' => $row[7], + 'njop_bumi' => $row[8], + 'njop_bangunan' => $row[9], + 'ketetapan' => $row[10], + 'tahun_pajak' => $row[11], + ]); + } + } + } +} diff --git a/app/Models/BusinessOrIndustry.php b/app/Models/BusinessOrIndustry.php new file mode 100644 index 0000000..9873f2e --- /dev/null +++ b/app/Models/BusinessOrIndustry.php @@ -0,0 +1,24 @@ +id(); + $table->string('nama_kecamatan'); + $table->string('nama_kelurahan'); + $table->string('nop')->unique(); + $table->string('nama_wajib_pajak'); + $table->text('alamat_wajib_pajak')->nullable(); + $table->text('alamat_objek_pajak'); + $table->decimal('luas_bumi',20,2)->default(0); + $table->decimal('luas_bangunan',20,2)->default(0); + $table->decimal('njop_bumi',20,2)->default(0); + $table->decimal('njop_bangunan',20,2)->default(0); + $table->decimal('ketetapan',20,2)->default(0); + $table->integer('tahun_pajak'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('business_or_industries'); + } +}; diff --git a/database/seeders/UsersRoleMenuSeeder.php b/database/seeders/UsersRoleMenuSeeder.php index 7003cad..0bd7771 100644 --- a/database/seeders/UsersRoleMenuSeeder.php +++ b/database/seeders/UsersRoleMenuSeeder.php @@ -148,7 +148,14 @@ class UsersRoleMenuSeeder extends Seeder "url" => "advertisements.index", "icon" => null, "parent_id" => $data->id, - "sort_order" => 1, + "sort_order" => 2, + ], + [ + "name" => "Usaha atau Industri", + "url" => "business-industries.index", + "icon" => null, + "parent_id" => $data->id, + "sort_order" => 2, ], ]; @@ -165,6 +172,7 @@ class UsersRoleMenuSeeder extends Seeder $setting_dashboard = Menu::where('name', 'Setting Dashboard')->first(); $setting_pbg = Menu::where('name', 'PBG')->first(); $reklame = Menu::where('name', 'Reklame')->first(); + $businessIndustries = Menu::where('name', 'Usaha atau Industri')->first(); // Superadmin gets all menus $superadmin->menus()->sync([ @@ -184,6 +192,7 @@ class UsersRoleMenuSeeder extends Seeder $setting_dashboard->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $setting_pbg->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $reklame->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], + $businessIndustries->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], ]); // Admin gets limited menus diff --git a/resources/js/business-industries/create.js b/resources/js/business-industries/create.js new file mode 100644 index 0000000..d6fa977 --- /dev/null +++ b/resources/js/business-industries/create.js @@ -0,0 +1,125 @@ +import { Dropzone } from "dropzone"; +import GlobalConfig from "../global-config"; +Dropzone.autoDiscover = false; + +var previewTemplate, + dropzone, + dropzonePreviewNode = document.querySelector("#dropzone-preview-list"); +console.log(previewTemplate); +console.log(dropzone); +console.log(dropzonePreviewNode); + +const toastNotification = document.getElementById("toastNotification"); +const toast = new bootstrap.Toast(toastNotification); + +(dropzonePreviewNode.id = ""), + dropzonePreviewNode && + ((previewTemplate = dropzonePreviewNode.parentNode.innerHTML), + dropzonePreviewNode.parentNode.removeChild(dropzonePreviewNode), + (dropzone = new Dropzone(".dropzone", { + url: `${GlobalConfig.apiHost}/api/api-business-industries/upload`, + method: "post", + acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation + previewTemplate: previewTemplate, + previewsContainer: "#dropzone-preview", + autoProcessQueue: false, // Disable auto post + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + }, + init: function () { + // Listen for the success event + this.on("success", function (file, response) { + console.log("File successfully uploaded:", file); + console.log("API Response:", response); + + // Show success toast + document.getElementById("toast-message").innerText = + response.message; + toast.show(); + document.getElementById("submit-upload").innerHTML = + "Upload Files"; + // Tunggu sebentar lalu reload halaman + setTimeout(() => { + window.location.href = "/data/business-industries"; + }, 2000); + }); + // Listen for the error event + this.on("error", function (file, errorMessage) { + console.error("Error uploading file:", file); + console.error("Error message:", errorMessage); + // Handle the error response + + // Show error toast + document.getElementById("toast-message").innerText = + errorMessage.message; + toast.show(); + document.getElementById("submit-upload").innerHTML = + "Upload Files"; + }); + }, + }))); + +// Add event listener to control the submission manually +document.querySelector("#submit-upload").addEventListener("click", function () { + console.log("Ini adalah value dropzone", dropzone.files[0]); + const formData = new FormData(); + console.log("Dropzonefiles", dropzone.files); + + this.innerHTML = + 'Loading...'; + + // Pastikan ada file dalam queue sebelum memprosesnya + if (dropzone.files.length > 0) { + formData.append("file", dropzone.files[0]); + console.log("ini adalah form data on submit", ...formData); + dropzone.processQueue(); // Ini akan manual memicu upload + } else { + // Show error toast when no file is selected + document.getElementById("toast-message").innerText = + "Please add a file first."; + toast.show(); + + document.getElementById("submit-upload").innerHTML = "Upload Files"; + } +}); + +// Optional: Listen for the 'addedfile' event to log or control file add behavior +dropzone.on("addedfile", function (file) { + console.log("File ditambahkan:", file); + console.log("Nama File:", file.name); + console.log("Tipe File:", file.type); + console.log("Ukuran File:", (file.size / 1024).toFixed(2) + " KB"); +}); + +dropzone.on("complete", function (file) { + dropzone.removeFile(file); +}); + +// Function to show toast +// function showToast(iconClass, iconColor, message) { +// const toastElement = document.getElementById("toastUploadAdvertisement"); +// const toastBody = toastElement.querySelector(".toast-body"); +// const toastHeader = toastElement.querySelector(".toast-header"); + +// // Remove existing icon (if any) before adding the new one +// const existingIcon = toastHeader.querySelector(".bx"); +// if (existingIcon) { +// toastHeader.querySelector(".auth-logo").removeChild(existingIcon); // Remove the existing icon +// } + +// // Add the new icon to the toast header +// const icon = document.createElement("i"); +// icon.classList.add("bx", iconClass); +// icon.style.fontSize = "25px"; +// icon.style.color = iconColor; +// toastHeader.querySelector(".auth-logo").appendChild(icon); + +// // Set the toast message +// toastBody.textContent = message; + +// // Show the toast +// const toast = new bootstrap.Toast(toastElement); // Inisialisasi Bootstrap Toast +// toast.show(); +// } diff --git a/resources/js/business-industries/index.js b/resources/js/business-industries/index.js new file mode 100644 index 0000000..a88e7a4 --- /dev/null +++ b/resources/js/business-industries/index.js @@ -0,0 +1,260 @@ +import { Grid } from "gridjs/dist/gridjs.umd.js"; +import gridjs from "gridjs/dist/gridjs.umd.js"; +import "gridjs/dist/gridjs.umd.js"; +import GlobalConfig from "../global-config.js"; + +class BusinessIndustries { + constructor() { + this.table = null; // Store Grid.js instance + } + init() { + this.getFetchApiData(); + } + + getFetchApiData() { + let tableContainer = document.getElementById( + "table-business-industries" + ); + + if (this.table) { + // If table exists, update its data instead of recreating + this.table + .updateConfig({ + server: { + url: `${GlobalConfig.apiHost}/api/api-business-industries`, + credentials: "include", + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + "Content-Type": "application/json", + }, + then: (data) => + data.data.map((item) => [ + item.id, + item.nama_kecamatan, + item.nama_kelurahan, + item.nop, + item.nama_wajib_pajak, + item.alamat_wajib_pajak, + item.alamat_objek_pajak, + item.luas_bumi, + item.luas_bangunan, + item.njop_bumi, + item.njop_bangunan, + item.ketetapan, + item.tahun_pajak, + item.created_at, + item.id, + ]), + total: (data) => data.total, + }, + }) + .forceRender(); + return; + } + + this.table = new Grid({ + columns: [ + { name: "ID", width: "80px", hidden: false }, + { name: "Nama Kecamatan", width: "200px" }, + { name: "Nama Kelurahan", width: "200px" }, + { name: "NOP", width: "150px" }, + { name: "Nama Wajib Pajak", width: "250px" }, + { name: "Alamat Wajib Pajak", width: "300px" }, + { name: "Alamat Objek Pajak", width: "300px" }, + { name: "Luas Bumi", width: "150px" }, + { name: "Luas Bangunan", width: "150px" }, + { name: "NJOP Bumi", width: "150px" }, + { name: "NJOP Bangunan", width: "150px" }, + { name: "Ketetapan", width: "150px" }, + { name: "Tahun Pajak", width: "120px" }, + { name: "Created", width: "180px" }, + { + name: "Actions", + width: "120px", + formatter: function (cell) { + return gridjs.html(` +