From d49035ce8d772ab2f37035a59c3dccfb1e65dc98 Mon Sep 17 00:00:00 2001 From: arifal hidayat Date: Sun, 23 Feb 2025 06:42:13 +0700 Subject: [PATCH] fix js menus and handle api menus --- app/Http/Controllers/Api/MenusController.php | 67 +++++++- app/Http/Requests/MenuRequest.php | 13 +- app/Http/Resources/MenuResource.php | 19 +++ resources/js/customers/index.js | 4 +- resources/js/menus/create.js | 104 ++++++------ resources/js/menus/index.js | 164 +++++++------------ resources/js/menus/update.js | 108 ++++++------ resources/views/menus/create.blade.php | 2 +- resources/views/menus/edit.blade.php | 2 +- routes/api.php | 7 +- 10 files changed, 278 insertions(+), 212 deletions(-) create mode 100644 app/Http/Resources/MenuResource.php diff --git a/app/Http/Controllers/Api/MenusController.php b/app/Http/Controllers/Api/MenusController.php index 95913c6..eede7aa 100644 --- a/app/Http/Controllers/Api/MenusController.php +++ b/app/Http/Controllers/Api/MenusController.php @@ -3,8 +3,11 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; +use App\Http\Requests\MenuRequest; +use App\Http\Resources\MenuResource; use App\Models\Menu; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Log; class MenusController extends Controller { @@ -13,7 +16,7 @@ class MenusController extends Controller */ public function index(Request $request) { - $query = Menu::query(); + $query = Menu::query()->orderBy('id', 'desc'); if($request->has("search") && !empty($request->get("search"))){ $query = $query->where("name", "like", "%".$request->get("search")."%"); @@ -25,9 +28,15 @@ class MenusController extends Controller /** * Store a newly created resource in storage. */ - public function store(Request $request) + public function store(MenuRequest $request) { - // + try{ + $menu = Menu::create($request->validated()); + return response()->json(['message' => 'Menu created successfully', 'data' => new MenuResource($menu)]); + }catch(\Exception $e){ + Log::error($e); + return response()->json(['message' => 'Error when creating menu'], 500); + } } /** @@ -35,15 +44,37 @@ class MenusController extends Controller */ public function show(string $id) { - // + try{ + $menu = Menu::find($id); + if($menu){ + return response()->json(['message' => 'Menu found', 'data' => new MenuResource($menu)]); + } else { + return response()->json(['message' => 'Menu not found'], 404); + } + }catch(\Exception $e){ + Log::error($e); + Log::error($e->getMessage()); + return response()->json(['message' => 'Error when finding menu'], 500); + } } /** * Update the specified resource in storage. */ - public function update(Request $request, string $id) + public function update(MenuRequest $request, string $id) { - // + try{ + $menu = Menu::findOrFail($id); + if($menu){ + $menu->update($request->validated()); + return response()->json(['message' => 'Menu updated successfully', 'data' => new MenuResource($menu)]); + } else { + return response()->json(['message' => 'Menu not found'], 404); + } + }catch(\Exception $e){ + Log::error($e); + return response()->json(['message' => 'Error when updating menu'], 500); + } } /** @@ -51,6 +82,28 @@ class MenusController extends Controller */ public function destroy(string $id) { - // + try{ + $menu = Menu::findOrFail($id); + if($menu){ + $this->deleteChildren($menu); + $menu->roles()->detach(); + $menu->delete(); + return response()->json(['message' => 'Menu deleted successfully']); + } else { + return response()->json(['message' => 'Menu not found'], 404); + } + }catch(\Exception $e){ + Log::error($e); + return response()->json(['message' => 'Error when deleting menu'], 500); + } + } + + private function deleteChildren($menu) + { + foreach ($menu->children as $child) { + $this->deleteChildren($child); // Recursively delete its children + $child->roles()->detach(); // Detach roles before deleting + $child->delete(); + } } } diff --git a/app/Http/Requests/MenuRequest.php b/app/Http/Requests/MenuRequest.php index a95e7e7..24bd50d 100644 --- a/app/Http/Requests/MenuRequest.php +++ b/app/Http/Requests/MenuRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; class MenuRequest extends FormRequest { @@ -21,12 +22,14 @@ class MenuRequest extends FormRequest */ public function rules(): array { + $menuId = $this->route('menu_id'); // Get the menu ID if updating + return [ - 'name' => ['required','string','max:255'], - 'url' => ['nullable','string','max:255'], - 'icon' => ['nullable','string','max:255'], - 'parent_id' => ['nullable','exists:menus,id'], - 'sort_order' => ['required','integer'], + 'name' => ['required', 'string', 'max:255', Rule::unique('menus', 'name')->ignore($menuId)], + 'url' => ['nullable', 'string', 'max:255'], + 'icon' => ['nullable', 'string', 'max:255'], + 'parent_id' => ['nullable', 'exists:menus,id'], + 'sort_order' => ['required', 'integer'], ]; } } diff --git a/app/Http/Resources/MenuResource.php b/app/Http/Resources/MenuResource.php new file mode 100644 index 0000000..7e67480 --- /dev/null +++ b/app/Http/Resources/MenuResource.php @@ -0,0 +1,19 @@ + + */ + public function toArray(Request $request): array + { + return parent::toArray($request); + } +} diff --git a/resources/js/customers/index.js b/resources/js/customers/index.js index 9141ae5..e2f5835 100644 --- a/resources/js/customers/index.js +++ b/resources/js/customers/index.js @@ -12,7 +12,7 @@ class Customers { this.table = null; // Initialize functions - this.initTableSpatialPlannings(); + this.initTableCustomers(); this.initEvents(); } initEvents() { @@ -25,7 +25,7 @@ class Customers { }); } - initTableSpatialPlannings() { + initTableCustomers() { let tableContainer = document.getElementById("table-customers"); // Create a new Grid.js instance only if it doesn't exist this.table = new Grid({ diff --git a/resources/js/menus/create.js b/resources/js/menus/create.js index 8ab2a1d..129004e 100644 --- a/resources/js/menus/create.js +++ b/resources/js/menus/create.js @@ -1,55 +1,67 @@ -document.addEventListener("DOMContentLoaded", function (e) { - const toastNotification = document.getElementById("toastNotification"); - const toast = new bootstrap.Toast(toastNotification); - document - .getElementById("btnCreateMenus") - .addEventListener("click", async function () { - let submitButton = this; - let spinner = document.getElementById("spinner"); - let form = document.getElementById("formCreateMenus"); +class CreateMenu { + constructor() { + this.initCreateMenu(); + } - if (!form) { - console.error("Form element not found!"); - return; - } - // Get form data - let formData = new FormData(form); + initCreateMenu() { + const toastNotification = document.getElementById("toastNotification"); + const toast = new bootstrap.Toast(toastNotification); + document + .getElementById("btnCreateMenus") + .addEventListener("click", async function () { + let submitButton = this; + let spinner = document.getElementById("spinner"); + let form = document.getElementById("formCreateMenus"); - // Disable button and show spinner - submitButton.disabled = true; - spinner.classList.remove("d-none"); + if (!form) { + console.error("Form element not found!"); + return; + } + // Get form data + let formData = new FormData(form); - try { - let response = await fetch(form.action, { - method: "POST", - headers: { - "X-CSRF-TOKEN": document - .querySelector('meta[name="csrf-token"]') - .getAttribute("content"), - }, - body: formData, - }); + // Disable button and show spinner + submitButton.disabled = true; + spinner.classList.remove("d-none"); - if (response.ok) { - let result = await response.json(); - document.getElementById("toast-message").innerText = - result.message; - toast.show(); - setTimeout(() => { - window.location.href = "/menus"; - }, 2000); - } else { - let error = await response.json(); + try { + let response = await fetch(form.action, { + method: "POST", + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + }, + body: formData, + }); + + if (response.ok) { + let result = await response.json(); + document.getElementById("toast-message").innerText = + result.message; + toast.show(); + setTimeout(() => { + window.location.href = "/menus"; + }, 2000); + } else { + let error = await response.json(); + document.getElementById("toast-message").innerText = + error.message; + toast.show(); + console.error("Error:", error); + submitButton.disabled = false; + spinner.classList.add("d-none"); + } + } catch (error) { + console.error("Request failed:", error); document.getElementById("toast-message").innerText = error.message; toast.show(); - console.error("Error:", error); } - } catch (error) { - console.error("Request failed:", error); - document.getElementById("toast-message").innerText = - error.message; - toast.show(); - } - }); + }); + } +} + +document.addEventListener("DOMContentLoaded", function (e) { + new CreateMenu(); }); diff --git a/resources/js/menus/index.js b/resources/js/menus/index.js index 9a12b31..a2797c6 100644 --- a/resources/js/menus/index.js +++ b/resources/js/menus/index.js @@ -2,13 +2,27 @@ 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"; +import Swal from "sweetalert2"; class Menus { constructor() { + this.toastMessage = document.getElementById("toast-message"); + this.toastElement = document.getElementById("toastNotification"); + this.toast = new bootstrap.Toast(this.toastElement); this.table = null; - } - init() { + + // Initialize functions this.initTableMenus(); + this.initEvents(); + } + initEvents() { + document.body.addEventListener("click", async (event) => { + const deleteButton = event.target.closest(".btn-delete-menu"); + if (deleteButton) { + event.preventDefault(); + await this.handleDelete(deleteButton); + } + }); } initTableMenus() { @@ -19,7 +33,7 @@ class Menus { this.table .updateConfig({ server: { - url: `${GlobalConfig.apiHost}/api/api-menus`, + url: `${GlobalConfig.apiHost}/api/menus`, credentials: "include", headers: { Authorization: `Bearer ${document @@ -60,7 +74,8 @@ class Menus { - @@ -83,7 +98,7 @@ class Menus { }, }, server: { - url: `${GlobalConfig.apiHost}/api/api-menus`, + url: `${GlobalConfig.apiHost}/api/menus`, credentials: "include", headers: { Authorization: `Bearer ${document @@ -104,118 +119,63 @@ class Menus { total: (data) => data.total, }, }).render(tableContainer); - - document.addEventListener("click", this.handleDelete.bind(this)); } - handleDelete(event) { - if (event.target.classList.contains("btn-delete-menu")) { - event.preventDefault(); - const id = event.target.getAttribute("data-id"); - let modalElement = document.getElementById("modalConfirmation"); - let toastMessage = document.getElementById("toast-message"); + async handleDelete(button) { + const id = button.getAttribute("data-id"); - if (!modalElement) { - console.error("Modal element not found!"); - return; - } + 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!", + }); - let modal = new bootstrap.Modal(modalElement); - let btnSaveConfirmation = document.getElementById( - "btnSaveConfirmation" - ); - let toastElement = document.getElementById("toastNotification"); - let toast = new bootstrap.Toast(toastElement); - - // Remove previous event listeners to avoid multiple bindings - btnSaveConfirmation.replaceWith( - btnSaveConfirmation.cloneNode(true) - ); - btnSaveConfirmation = document.getElementById( - "btnSaveConfirmation" - ); - - // Set the role ID on the confirm button inside the modal - btnSaveConfirmation.setAttribute("data-menu-id", id); - - // Show the modal - modal.show(); - - btnSaveConfirmation.addEventListener("click", async () => { - let menuId = btnSaveConfirmation.getAttribute("data-menu-id"); - - try { - let response = await fetch(`/menus/${menuId}`, { + if (result.isConfirmed) { + try { + let response = await fetch( + `${GlobalConfig.apiHost}/api/menus/${id}`, + { method: "DELETE", credentials: "include", - headers: { - "X-CSRF-TOKEN": document - .querySelector('meta[name="csrf-token"]') - .getAttribute("content"), - "Content-Type": "application/json", - }, - }); - - if (response.ok) { - let result = await response.json(); - toastMessage.innerText = - result.message || "Deleted successfully!"; - toast.show(); - - // Hide modal - modal.hide(); - - // Refresh Grid.js table - this.refreshTableMenus(); - } else { - let error = await response.json(); - console.error("Delete failed:", error); - toastMessage.innerText = - error.message || "Delete failed!"; - toast.show(); - } - } catch (error) { - console.error("Error deleting item:", error); - toastMessage.innerText = "An error occurred!"; - toast.show(); - } - }); - } - } - - refreshTableMenus() { - if (this.table) { - this.table - .updateConfig({ - server: { - url: `${GlobalConfig.apiHost}/api/api-menus`, - 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.name, - item.url, - item.icon, - item.parent_id, - item.sort_order, - item.id, - ]), - total: (data) => data.total, - }, - }) - .forceRender(); - } else { - this.initTableMenus(); // If no table exists, reinitialize it + } + ); + + 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 Menus().init(); + new Menus(); }); diff --git a/resources/js/menus/update.js b/resources/js/menus/update.js index a5024ee..44d53fe 100644 --- a/resources/js/menus/update.js +++ b/resources/js/menus/update.js @@ -1,53 +1,67 @@ -document.addEventListener("DOMContentLoaded", function (e) { - let form = document.getElementById("formUpdateMenus"); - let submitButton = document.getElementById("btnUpdateMenus"); - let spinner = document.getElementById("spinner"); - let toastMessage = document.getElementById("toast-message"); - let toast = new bootstrap.Toast( - document.getElementById("toastNotification") - ); - submitButton.addEventListener("click", async function () { - let submitButton = this; +class UpdateMenu { + constructor() { + this.initUpdateMenu(); + } - if (!form) { - console.error("Form element not found!"); - return; - } - // Get form data - let formData = new FormData(form); + initUpdateMenu() { + const toastNotification = document.getElementById("toastNotification"); + const toast = new bootstrap.Toast(toastNotification); + document + .getElementById("btnUpdateMenus") + .addEventListener("click", async function () { + let submitButton = this; + let spinner = document.getElementById("spinner"); + let form = document.getElementById("formUpdateMenus"); - // Disable button and show spinner - submitButton.disabled = true; - spinner.classList.remove("d-none"); + if (!form) { + console.error("Form element not found!"); + return; + } + // Get form data + let formData = new FormData(form); - try { - let response = await fetch(form.action, { - method: "POST", - headers: { - "X-CSRF-TOKEN": document - .querySelector('meta[name="csrf-token"]') - .getAttribute("content"), - }, - body: formData, + // Disable button and show spinner + submitButton.disabled = true; + spinner.classList.remove("d-none"); + + try { + let response = await fetch(form.action, { + method: "POST", + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + }, + body: formData, + }); + + if (response.ok) { + let result = await response.json(); + document.getElementById("toast-message").innerText = + result.message; + toast.show(); + setTimeout(() => { + window.location.href = "/menus"; + }, 2000); + } else { + let error = await response.json(); + document.getElementById("toast-message").innerText = + error.message; + toast.show(); + console.error("Error:", error); + submitButton.disabled = false; + spinner.classList.add("d-none"); + } + } catch (error) { + console.error("Request failed:", error); + document.getElementById("toast-message").innerText = + error.message; + toast.show(); + } }); + } +} - if (response.ok) { - let result = await response.json(); - toastMessage.innerText = result.message; - toast.show(); - setTimeout(() => { - window.location.href = "/menus"; - }, 2000); - } else { - let error = await response.json(); - toastMessage.innerText = error.message; - toast.show(); - console.error("Error:", error); - } - } catch (error) { - console.error("Request failed:", error); - toastMessage.innerText = error.message; - toast.show(); - } - }); +document.addEventListener("DOMContentLoaded", function (e) { + new UpdateMenu(); }); diff --git a/resources/views/menus/create.blade.php b/resources/views/menus/create.blade.php index 822c7f3..3786d66 100644 --- a/resources/views/menus/create.blade.php +++ b/resources/views/menus/create.blade.php @@ -13,7 +13,7 @@
-
+ @csrf
diff --git a/resources/views/menus/edit.blade.php b/resources/views/menus/edit.blade.php index e4628b0..d6467ec 100644 --- a/resources/views/menus/edit.blade.php +++ b/resources/views/menus/edit.blade.php @@ -13,7 +13,7 @@
- id)}}" method="post"> + id)}}" method="post"> @csrf @method("put")
diff --git a/routes/api.php b/routes/api.php index ae6755a..89a44dd 100644 --- a/routes/api.php +++ b/routes/api.php @@ -100,7 +100,12 @@ Route::group(['middleware' => 'auth:sanctum'], function (){ Route::get('/sync-task-submit/{uuid}', [SyncronizeController::class, 'syncTaskDetailSubmit'])->name('api.task.submit'); // menus api - Route::apiResource('api-menus', MenusController::class); + Route::controller(MenusController::class)->group(function (){ + Route::get('/menus', 'index')->name('api.menus'); + Route::post('/menus', 'store')->name('api.menus.store'); + Route::put('/menus/{menu_id}', 'update')->name('api.menus.update'); + Route::delete('/menus/{menu_id}', 'destroy')->name('api.menus.destroy'); + }); // roles api Route::apiResource('api-roles', RolesController::class);