fix js menus and handle api menus

This commit is contained in:
arifal hidayat
2025-02-23 06:42:13 +07:00
parent ffd9d3514c
commit d49035ce8d
10 changed files with 278 additions and 212 deletions

View File

@@ -3,8 +3,11 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\MenuRequest;
use App\Http\Resources\MenuResource;
use App\Models\Menu; use App\Models\Menu;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class MenusController extends Controller class MenusController extends Controller
{ {
@@ -13,7 +16,7 @@ class MenusController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$query = Menu::query(); $query = Menu::query()->orderBy('id', 'desc');
if($request->has("search") && !empty($request->get("search"))){ if($request->has("search") && !empty($request->get("search"))){
$query = $query->where("name", "like", "%".$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. * 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) 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. * 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) 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();
}
} }
} }

View File

@@ -3,6 +3,7 @@
namespace App\Http\Requests; namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class MenuRequest extends FormRequest class MenuRequest extends FormRequest
{ {
@@ -21,12 +22,14 @@ class MenuRequest extends FormRequest
*/ */
public function rules(): array public function rules(): array
{ {
$menuId = $this->route('menu_id'); // Get the menu ID if updating
return [ return [
'name' => ['required','string','max:255'], 'name' => ['required', 'string', 'max:255', Rule::unique('menus', 'name')->ignore($menuId)],
'url' => ['nullable','string','max:255'], 'url' => ['nullable', 'string', 'max:255'],
'icon' => ['nullable','string','max:255'], 'icon' => ['nullable', 'string', 'max:255'],
'parent_id' => ['nullable','exists:menus,id'], 'parent_id' => ['nullable', 'exists:menus,id'],
'sort_order' => ['required','integer'], 'sort_order' => ['required', 'integer'],
]; ];
} }
} }

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class MenuResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return parent::toArray($request);
}
}

View File

@@ -12,7 +12,7 @@ class Customers {
this.table = null; this.table = null;
// Initialize functions // Initialize functions
this.initTableSpatialPlannings(); this.initTableCustomers();
this.initEvents(); this.initEvents();
} }
initEvents() { initEvents() {
@@ -25,7 +25,7 @@ class Customers {
}); });
} }
initTableSpatialPlannings() { initTableCustomers() {
let tableContainer = document.getElementById("table-customers"); let tableContainer = document.getElementById("table-customers");
// Create a new Grid.js instance only if it doesn't exist // Create a new Grid.js instance only if it doesn't exist
this.table = new Grid({ this.table = new Grid({

View File

@@ -1,55 +1,67 @@
document.addEventListener("DOMContentLoaded", function (e) { class CreateMenu {
const toastNotification = document.getElementById("toastNotification"); constructor() {
const toast = new bootstrap.Toast(toastNotification); this.initCreateMenu();
document }
.getElementById("btnCreateMenus")
.addEventListener("click", async function () {
let submitButton = this;
let spinner = document.getElementById("spinner");
let form = document.getElementById("formCreateMenus");
if (!form) { initCreateMenu() {
console.error("Form element not found!"); const toastNotification = document.getElementById("toastNotification");
return; const toast = new bootstrap.Toast(toastNotification);
} document
// Get form data .getElementById("btnCreateMenus")
let formData = new FormData(form); .addEventListener("click", async function () {
let submitButton = this;
let spinner = document.getElementById("spinner");
let form = document.getElementById("formCreateMenus");
// Disable button and show spinner if (!form) {
submitButton.disabled = true; console.error("Form element not found!");
spinner.classList.remove("d-none"); return;
}
// Get form data
let formData = new FormData(form);
try { // Disable button and show spinner
let response = await fetch(form.action, { submitButton.disabled = true;
method: "POST", spinner.classList.remove("d-none");
headers: {
"X-CSRF-TOKEN": document
.querySelector('meta[name="csrf-token"]')
.getAttribute("content"),
},
body: formData,
});
if (response.ok) { try {
let result = await response.json(); let response = await fetch(form.action, {
document.getElementById("toast-message").innerText = method: "POST",
result.message; headers: {
toast.show(); Authorization: `Bearer ${document
setTimeout(() => { .querySelector('meta[name="api-token"]')
window.location.href = "/menus"; .getAttribute("content")}`,
}, 2000); },
} else { body: formData,
let error = await response.json(); });
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 = document.getElementById("toast-message").innerText =
error.message; error.message;
toast.show(); 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();
});
}); });

View File

@@ -2,13 +2,27 @@ import { Grid } from "gridjs/dist/gridjs.umd.js";
import gridjs from "gridjs/dist/gridjs.umd.js"; import gridjs from "gridjs/dist/gridjs.umd.js";
import "gridjs/dist/gridjs.umd.js"; import "gridjs/dist/gridjs.umd.js";
import GlobalConfig from "../global-config"; import GlobalConfig from "../global-config";
import Swal from "sweetalert2";
class Menus { class Menus {
constructor() { constructor() {
this.toastMessage = document.getElementById("toast-message");
this.toastElement = document.getElementById("toastNotification");
this.toast = new bootstrap.Toast(this.toastElement);
this.table = null; this.table = null;
}
init() { // Initialize functions
this.initTableMenus(); 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() { initTableMenus() {
@@ -19,7 +33,7 @@ class Menus {
this.table this.table
.updateConfig({ .updateConfig({
server: { server: {
url: `${GlobalConfig.apiHost}/api/api-menus`, url: `${GlobalConfig.apiHost}/api/menus`,
credentials: "include", credentials: "include",
headers: { headers: {
Authorization: `Bearer ${document Authorization: `Bearer ${document
@@ -60,7 +74,8 @@ class Menus {
<a href="/menus/${cell}/edit" class="btn btn-yellow btn-sm d-inline-flex align-items-center justify-content-center"> <a href="/menus/${cell}/edit" class="btn btn-yellow btn-sm d-inline-flex align-items-center justify-content-center">
<i class='bx bx-edit'></i> <i class='bx bx-edit'></i>
</a> </a>
<button data-id="${cell}" class="btn btn-red btn-sm btn-delete-menu d-inline-flex align-items-center justify-content-center"> <button data-id="${cell}"
class="btn btn-red btn-sm btn-delete-menu d-inline-flex align-items-center justify-content-center">
<i class='bx bxs-trash' ></i> <i class='bx bxs-trash' ></i>
</button> </button>
</div> </div>
@@ -83,7 +98,7 @@ class Menus {
}, },
}, },
server: { server: {
url: `${GlobalConfig.apiHost}/api/api-menus`, url: `${GlobalConfig.apiHost}/api/menus`,
credentials: "include", credentials: "include",
headers: { headers: {
Authorization: `Bearer ${document Authorization: `Bearer ${document
@@ -104,118 +119,63 @@ class Menus {
total: (data) => data.total, total: (data) => data.total,
}, },
}).render(tableContainer); }).render(tableContainer);
document.addEventListener("click", this.handleDelete.bind(this));
} }
handleDelete(event) { async handleDelete(button) {
if (event.target.classList.contains("btn-delete-menu")) { const id = button.getAttribute("data-id");
event.preventDefault();
const id = event.target.getAttribute("data-id");
let modalElement = document.getElementById("modalConfirmation");
let toastMessage = document.getElementById("toast-message");
if (!modalElement) { const result = await Swal.fire({
console.error("Modal element not found!"); title: "Are you sure?",
return; 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); if (result.isConfirmed) {
let btnSaveConfirmation = document.getElementById( try {
"btnSaveConfirmation" let response = await fetch(
); `${GlobalConfig.apiHost}/api/menus/${id}`,
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}`, {
method: "DELETE", method: "DELETE",
credentials: "include", 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: { headers: {
Authorization: `Bearer ${document Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]') .querySelector('meta[name="api-token"]')
.getAttribute("content")}`, .getAttribute("content")}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
then: (data) => }
data.data.map((item) => [ );
item.id,
item.name, if (response.ok) {
item.url, let result = await response.json();
item.icon, this.toastMessage.innerText =
item.parent_id, result.message || "Deleted successfully!";
item.sort_order, this.toast.show();
item.id,
]), // Refresh Grid.js table
total: (data) => data.total, if (typeof this.table !== "undefined") {
}, this.table.updateConfig({}).forceRender();
}) }
.forceRender(); } else {
} else { let error = await response.json();
this.initTableMenus(); // If no table exists, reinitialize it 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) { document.addEventListener("DOMContentLoaded", function (e) {
new Menus().init(); new Menus();
}); });

View File

@@ -1,53 +1,67 @@
document.addEventListener("DOMContentLoaded", function (e) { class UpdateMenu {
let form = document.getElementById("formUpdateMenus"); constructor() {
let submitButton = document.getElementById("btnUpdateMenus"); this.initUpdateMenu();
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;
if (!form) { initUpdateMenu() {
console.error("Form element not found!"); const toastNotification = document.getElementById("toastNotification");
return; const toast = new bootstrap.Toast(toastNotification);
} document
// Get form data .getElementById("btnUpdateMenus")
let formData = new FormData(form); .addEventListener("click", async function () {
let submitButton = this;
let spinner = document.getElementById("spinner");
let form = document.getElementById("formUpdateMenus");
// Disable button and show spinner if (!form) {
submitButton.disabled = true; console.error("Form element not found!");
spinner.classList.remove("d-none"); return;
}
// Get form data
let formData = new FormData(form);
try { // Disable button and show spinner
let response = await fetch(form.action, { submitButton.disabled = true;
method: "POST", spinner.classList.remove("d-none");
headers: {
"X-CSRF-TOKEN": document try {
.querySelector('meta[name="csrf-token"]') let response = await fetch(form.action, {
.getAttribute("content"), method: "POST",
}, headers: {
body: formData, 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) { document.addEventListener("DOMContentLoaded", function (e) {
let result = await response.json(); new UpdateMenu();
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();
}
});
}); });

View File

@@ -13,7 +13,7 @@
<div class="col-md-6"> <div class="col-md-6">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<form id="formCreateMenus" action="{{route("menus.store")}}" method="post"> <form id="formCreateMenus" action="{{route("api.menus.store")}}" method="post">
@csrf @csrf
<div class="mb-3"> <div class="mb-3">
<label class="form-label" for="name">Name</label> <label class="form-label" for="name">Name</label>

View File

@@ -13,7 +13,7 @@
<div class="col-md-6"> <div class="col-md-6">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<form id="formUpdateMenus" action="{{route("menus.update", $menu->id)}}" method="post"> <form id="formUpdateMenus" action="{{route("api.menus.update", $menu->id)}}" method="post">
@csrf @csrf
@method("put") @method("put")
<div class="mb-3"> <div class="mb-3">

View File

@@ -100,7 +100,12 @@ Route::group(['middleware' => 'auth:sanctum'], function (){
Route::get('/sync-task-submit/{uuid}', [SyncronizeController::class, 'syncTaskDetailSubmit'])->name('api.task.submit'); Route::get('/sync-task-submit/{uuid}', [SyncronizeController::class, 'syncTaskDetailSubmit'])->name('api.task.submit');
// menus api // 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 // roles api
Route::apiResource('api-roles', RolesController::class); Route::apiResource('api-roles', RolesController::class);