create loading and handle from js create edit and delete roles

This commit is contained in:
arifal
2025-02-11 23:40:31 +07:00
parent 2bf4b8b327
commit 1a15bc03f8
20 changed files with 422 additions and 153 deletions

View File

@@ -35,18 +35,18 @@ class RolesController extends Controller
{ {
try{ try{
$request->validate([ $request->validate([
"name" => "required", "name" => "required|unique:roles,name",
"description" => "nullable", "description" => "nullable",
]); ]);
DB::beginTransaction(); DB::beginTransaction();
Role::create($request->all()); Role::create($request->all());
DB::commit(); DB::commit();
return redirect()->route("roles.index")->with('success','Succesfully Created'); return response()->json(['message' => 'Role created successfully'], 201);
} }
catch(\Exception $e){ catch(\Exception $e){
DB::rollBack(); DB::rollBack();
return redirect()->back()->with("error", $e->getMessage()); return response()->json(['message' => $e->getMessage()], 500);
} }
} }
@@ -83,10 +83,10 @@ class RolesController extends Controller
DB::beginTransaction(); DB::beginTransaction();
$role->update($validatedData); $role->update($validatedData);
DB::commit(); DB::commit();
return redirect()->route('roles.index')->with('success','Successfully updated'); return response()->json(['message' => 'Role updated successfully'], 200);
}catch(\Exception $e){ }catch(\Exception $e){
DB::rollBack(); DB::rollBack();
return redirect()->back()->with("error", $e->getMessage()); return response()->json(['message' => $e->getMessage()], 500);
} }
} }
@@ -97,11 +97,13 @@ class RolesController extends Controller
{ {
try{ try{
DB::beginTransaction(); DB::beginTransaction();
Role::findOrFail($id)->delete(); $deleted = Role::findOrFail($id)->delete();
info("deleted". $deleted);
DB::commit(); DB::commit();
return response()->json(array('success' => true, "message" => "Successfully deleted"));
}catch(\Exception $e){ }catch(\Exception $e){
DB::rollBack(); DB::rollBack();
return redirect()->back()->with("error", $e->getMessage()); return response()->json(array('success' => false, "message" => $e->getMessage()));
} }
} }

View File

@@ -0,0 +1,25 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class ModalConfirmation extends Component
{
/**
* Create a new component instance.
*/
public function __construct()
{
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.modal-confirmation');
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class ToastNotification extends Component
{
/**
* Create a new component instance.
*/
public function __construct()
{
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.toast-notification');
}
}

View File

@@ -160,11 +160,13 @@ class UsersRoleMenuSeeder extends Seeder
// Superadmin gets all menus // Superadmin gets all menus
$superadmin->menus()->sync([ $superadmin->menus()->sync([
$dashboard->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], // parent
$master->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $dashboard->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false],
$settings->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $master->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false],
$dataSettings->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $settings->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false],
$data->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $dataSettings->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false],
$data->id => ["allow_show" => true, "allow_create" => false, "allow_update" => false, "allow_destroy" => false],
// children
$dashboard_pimpinan->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $dashboard_pimpinan->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$dashboard_pbg->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $dashboard_pbg->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$users->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true], $users->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],

View File

@@ -0,0 +1,55 @@
document.addEventListener("DOMContentLoaded", function (e) {
const toastNotification = document.getElementById("toastNotification");
const toast = new bootstrap.Toast(toastNotification);
document
.getElementById("btnCreateRole")
.addEventListener("click", async function () {
let submitButton = this;
let spinner = document.getElementById("spinner");
let form = document.getElementById("formCreateRole");
if (!form) {
console.error("Form element not found!");
return;
}
// Get form data
let formData = new FormData(form);
// Disable button and show spinner
submitButton.disabled = true;
spinner.classList.remove("d-none");
try {
let response = await fetch(form.action, {
method: "POST",
headers: {
"X-CSRF-TOKEN": document
.querySelector('meta[name="csrf-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 = "/roles";
}, 2000);
} else {
let error = await response.json();
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();
}
});
});

View File

@@ -67,11 +67,37 @@ class Roles {
handleDelete(event) { handleDelete(event) {
if (event.target.classList.contains("btn-delete-role")) { if (event.target.classList.contains("btn-delete-role")) {
event.preventDefault(); event.preventDefault();
const id = event.target.getAttribute("data-id");
if (confirm("Are you sure you want to delete this item?")) { const id = event.target.getAttribute("data-id");
fetch(`/roles/${id}`, { let modalElement = document.getElementById("modalConfirmation");
if (!modalElement) {
console.error("Modal element not found!");
return;
}
let modal = new bootstrap.Modal(modalElement);
// Set the role ID on the confirm button inside the modal
let btnSaveConfirmation = document.getElementById(
"btnSaveConfirmation"
);
btnSaveConfirmation.setAttribute("data-role-id", id);
let toastElement = document.getElementById("toastNotificationApi");
let toast = new bootstrap.Toast(toastElement);
// Show the modal
modal.show();
// Prevent multiple event listeners
btnSaveConfirmation.onclick = function () {
let roleId = this.getAttribute("data-role-id");
console.log("Deleted ID:", roleId);
fetch(`/roles/${roleId}`, {
method: "DELETE", method: "DELETE",
credentials: "include",
headers: { headers: {
"X-CSRF-TOKEN": document "X-CSRF-TOKEN": document
.querySelector('meta[name="csrf-token"]') .querySelector('meta[name="csrf-token"]')
@@ -81,21 +107,21 @@ class Roles {
}) })
.then((response) => { .then((response) => {
if (response.ok) { if (response.ok) {
alert("Item deleted successfully!"); modal.hide();
window.location.reload(); toast.show();
setTimeout(() => window.location.reload(), 1500);
} else { } else {
return response.json().then((error) => { return response.json().then((error) => {
throw new Error( console.error("Delete failed:", error);
error.message || "Failed to delete item." toast.show();
);
}); });
} }
}) })
.catch((error) => { .catch((error) => {
console.error("Error deleting item:", error); console.error("Error deleting item:", error);
alert("Something went wrong. Please try again."); toast.show();
}); });
} };
} }
} }
} }

View File

@@ -0,0 +1,53 @@
document.addEventListener("DOMContentLoaded", function (e) {
let form = document.getElementById("formUpdateRole");
let submitButton = document.getElementById("btnUpdateRole");
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) {
console.error("Form element not found!");
return;
}
// Get form data
let formData = new FormData(form);
// Disable button and show spinner
submitButton.disabled = true;
spinner.classList.remove("d-none");
try {
let response = await fetch(form.action, {
method: "POST",
headers: {
"X-CSRF-TOKEN": document
.querySelector('meta[name="csrf-token"]')
.getAttribute("content"),
},
body: formData,
});
if (response.ok) {
let result = await response.json();
toastMessage.innerText = result.message;
toast.show();
setTimeout(() => {
window.location.href = "/roles";
}, 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

@@ -3,128 +3,141 @@
// //
.app-wrapper { .app-wrapper {
height: 100%; height: 100%;
margin: 0 auto; margin: 0 auto;
position: relative; position: relative;
} }
// Main Content // Main Content
.page-content { .page-content {
position: relative; position: relative;
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
min-height: calc(100vh - $topbar-height); min-height: calc(100vh - $topbar-height);
padding: calc($spacer * 1) calc($spacer * 0.5) $footer-height calc($spacer * 0.5); padding: calc($spacer * 1) calc($spacer * 0.5) $footer-height
margin-left: $sidebar-width; calc($spacer * 0.5);
margin-left: $sidebar-width;
} }
// Animated Star // Animated Star
$shooting-time: 3000ms; $shooting-time: 3000ms;
.animated-stars { .animated-stars {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
transform: rotateZ(45deg); transform: rotateZ(45deg);
} }
.shooting-star { .shooting-star {
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 50%; top: 50%;
height: 2px; height: 2px;
background: linear-gradient(-45deg, rgba(95, 145, 255, 1), rgba(0, 0, 255, 0)); background: linear-gradient(
border-radius: 999px; -45deg,
filter: drop-shadow(0 0 6px rgba(105, 155, 255, 1)); rgba(95, 145, 255, 1),
animation: rgba(0, 0, 255, 0)
tail $shooting-time ease-in-out infinite, );
shooting $shooting-time ease-in-out infinite; border-radius: 999px;
filter: drop-shadow(0 0 6px rgba(105, 155, 255, 1));
animation: tail $shooting-time ease-in-out infinite,
shooting $shooting-time ease-in-out infinite;
&::before { &::before {
content: ''; content: "";
position: absolute; position: absolute;
top: calc(50% - 1px); top: calc(50% - 1px);
right: 0; right: 0;
height: 2px; height: 2px;
background: linear-gradient(-45deg, rgba(0, 0, 255, 0), rgba(95, 145, 255, 1), rgba(0, 0, 255, 0)); background: linear-gradient(
transform: translateX(50%) rotateZ(45deg); -45deg,
border-radius: 100%; rgba(0, 0, 255, 0),
animation: shining $shooting-time ease-in-out infinite; rgba(95, 145, 255, 1),
} rgba(0, 0, 255, 0)
);
transform: translateX(50%) rotateZ(45deg);
border-radius: 100%;
animation: shining $shooting-time ease-in-out infinite;
}
&::after { &::after {
content: ''; content: "";
position: absolute; position: absolute;
top: calc(50% - 1px); top: calc(50% - 1px);
right: 0; right: 0;
height: 2px; height: 2px;
background: linear-gradient(-45deg, rgba(0, 0, 255, 0), rgba(95, 145, 255, 1), rgba(0, 0, 255, 0)); background: linear-gradient(
transform: translateX(50%) rotateZ(45deg); -45deg,
border-radius: 100%; rgba(0, 0, 255, 0),
animation: shining $shooting-time ease-in-out infinite; rgba(95, 145, 255, 1),
transform: translateX(50%) rotateZ(-45deg); rgba(0, 0, 255, 0)
} );
transform: translateX(50%) rotateZ(45deg);
border-radius: 100%;
animation: shining $shooting-time ease-in-out infinite;
transform: translateX(50%) rotateZ(-45deg);
}
@for $i from 1 through 20 { @for $i from 1 through 20 {
&:nth-child(#{$i}) { &:nth-child(#{$i}) {
$delay: random(9999) + 0ms; $delay: random(9999) + 0ms;
top: calc(50% - #{random(400) - 200px}); top: calc(50% - #{random(400) - 200px});
left: calc(50% - #{random(300) + 0px}); left: calc(50% - #{random(300) + 0px});
animation-delay: $delay; animation-delay: $delay;
// opacity: random(50) / 100 + 0.5; // opacity: random(50) / 100 + 0.5;
&::before, &::before,
&::after { &::after {
animation-delay: $delay; animation-delay: $delay;
} }
} }
} }
} }
@keyframes tail { @keyframes tail {
0% { 0% {
width: 0; width: 0;
} }
30% { 30% {
width: 100px; width: 100px;
} }
100% { 100% {
width: 0; width: 0;
} }
} }
@keyframes shining { @keyframes shining {
0% { 0% {
width: 0; width: 0;
} }
50% { 50% {
width: 30px; width: 30px;
} }
100% { 100% {
width: 0; width: 0;
} }
} }
@keyframes shooting { @keyframes shooting {
0% { 0% {
transform: translateX(0); transform: translateX(0);
} }
100% { 100% {
transform: translateX(300px); transform: translateX(300px);
} }
} }
@keyframes sky { @keyframes sky {
0% { 0% {
transform: rotate(45deg); transform: rotate(45deg);
} }
100% { 100% {
transform: rotate(45 + 360deg); transform: rotate(45 + 360deg);
} }
} }

View File

@@ -0,0 +1,17 @@
@props(['buttonText' => 'Confirm', 'confirmationMessage' => 'Are you sure?'])
<div class="modal fade" id="modalConfirmation" tabindex="-1"
aria-labelledby="modalConfirmationTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-body">
<p class="confirmation-message">{{$confirmationMessage}}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-bs-dismiss="modal" id="btnCloseModal">Close</button>
<button type="button" class="btn btn-primary" id="btnSaveConfirmation">{{$buttonText}}</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,14 @@
<div class="toast-container position-fixed end-0 top-0 p-3">
<div id="toastNotification" class="toast align-items-center" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<div class="auth-logo me-auto">
</div>
<small class="text-muted">{{now()->format("Y-m-d H:i:s")}}</small>
<button type="button" class="btn-close" data-bs-dismiss="toast"
aria-label="Close"></button>
</div>
<div class="toast-body">
<p id="toast-message"></p>
</div>
</div>
</div>

View File

@@ -9,11 +9,13 @@
@include('layouts.partials/page-title', ['title' => 'Data Settings', 'subtitle' => 'Setting Dashboard']) @include('layouts.partials/page-title', ['title' => 'Data Settings', 'subtitle' => 'Setting Dashboard'])
<div class="row"> <div class="row">
<div class="d-flex justify-content-end pb-3"> <div class="card w-full">
<a href="{{ route('data-settings.create')}}" class="btn btn-success width-lg">Create</a> <div class="card-body">
</div> <div class="d-flex justify-content-end pb-3">
<div> <a href="{{ route('data-settings.create')}}" class="btn btn-success width-lg">Create</a>
<div id="table-data-settings"></div> </div>
<div id="table-data-settings"></div>
</div>
</div> </div>
</div> </div>

View File

@@ -9,12 +9,13 @@
@include('layouts.partials/page-title', ['title' => 'Master', 'subtitle' => 'Users']) @include('layouts.partials/page-title', ['title' => 'Master', 'subtitle' => 'Users'])
<div class="row"> <div class="row">
<div class="d-flex justify-content-end pb-3"> <div class="card w-full">
<a href="{{ route('users.create')}}" class="btn btn-success width-lg">Create</a> <div class="card-body">
</div> <div class="d-flex justify-content-end pb-3">
{{$users}} <a href="{{ route('users.create')}}" class="btn btn-success width-lg">Create</a>
<div> </div>
<div id="table-users"></div> <div id="table-users"></div>
</div>
</div> </div>
</div> </div>

View File

@@ -9,11 +9,15 @@
@include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Menu']) @include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Menu'])
<div class="row"> <div class="row">
<div class="d-flex justify-content-end pb-3"> <div class="card w-full">
<a href="{{ route('menus.create')}}" class="btn btn-success width-lg">Create</a> <div class="card-body">
</div> <div class="d-flex justify-content-end pb-3">
<div> <a href="{{ route('menus.create')}}" class="btn btn-success width-lg">Create</a>
<div id="table-menus"></div> </div>
<div>
<div id="table-menus"></div>
</div>
</div>
</div> </div>
</div> </div>

View File

@@ -9,11 +9,13 @@
@include('layouts.partials/page-title', ['title' => 'Data', 'subtitle' => 'PBG']) @include('layouts.partials/page-title', ['title' => 'Data', 'subtitle' => 'PBG'])
<div class="row"> <div class="row">
<div class="d-flex justify-content-end pb-3"> <div class="card w-full">
<a href="{{ route('pbg-task.create')}}" class="btn btn-success width-lg">Create</a> <div class="card-body">
</div> <div class="d-flex justify-content-end pb-3">
<div> <a href="{{ route('pbg-task.create')}}" class="btn btn-success width-lg">Create</a>
<div id="table-pbg-tasks"></div> </div>
<div id="table-pbg-tasks"></div>
</div>
</div> </div>
</div> </div>

View File

@@ -4,11 +4,12 @@
@include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Role']) @include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Role'])
<x-toast-notification />
<div class="row d-flex justify-content-center"> <div class="row d-flex justify-content-center">
<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 action="{{route("roles.store")}}" method="post"> <form action="{{route("roles.store")}}" method="post" id="formCreateRole" data-redirect="{{route("roles.index")}}">
@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>
@@ -20,7 +21,10 @@
<input type="text" id="description" name="description" <input type="text" id="description" name="description"
class="form-control" placeholder="Enter description"> class="form-control" placeholder="Enter description">
</div> </div>
<button type="submit" class="btn btn-success">Create</button> <button class="btn btn-primary me-1" type="button" id="btnCreateRole">
<span id="spinner" class="spinner-border spinner-border-sm me-1 d-none" role="status" aria-hidden="true"></span>
Create
</button>
</form> </form>
</div> </div>
</div> </div>
@@ -28,3 +32,7 @@
</div> </div>
@endsection @endsection
@section('scripts')
@vite(['resources/js/roles/create.js'])
@endsection

View File

@@ -4,11 +4,12 @@
@include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Role']) @include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Role'])
<x-toast-notification/>
<div class="row d-flex justify-content-center"> <div class="row d-flex justify-content-center">
<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 action="{{route("roles.update", $role->id)}}" method="post"> <form id="formUpdateRole" action="{{route("roles.update", $role->id)}}" method="post" >
@csrf @csrf
@method("put") @method("put")
<div class="mb-3"> <div class="mb-3">
@@ -21,7 +22,10 @@
<input type="text" id="description" name="description" <input type="text" id="description" name="description"
class="form-control" placeholder="Enter description" value="{{$role->description}}"> class="form-control" placeholder="Enter description" value="{{$role->description}}">
</div> </div>
<button type="submit" class="btn btn-success">Update</button> <button class="btn btn-primary me-1" type="button" id="btnUpdateRole">
<span id="spinner" class="spinner-border spinner-border-sm me-1 d-none" role="status" aria-hidden="true"></span>
Update
</button>
</form> </form>
</div> </div>
</div> </div>
@@ -29,3 +33,6 @@
</div> </div>
@endsection @endsection
@section('scripts')
@vite(['resources/js/roles/update.js'])
@endsection

View File

@@ -8,12 +8,17 @@
@include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Role']) @include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Role'])
<x-toast-notification/>
<x-modal-confirmation buttonText="Delete" confirmationMessage="Are you sure you want to delete this?" />
<div class="row"> <div class="row">
<div class="d-flex justify-content-end pb-3"> <div class="card w-full">
<a href="{{ route('roles.create')}}" class="btn btn-success width-lg">Create</a> <div class="card-body">
</div> <div class="d-flex justify-content-end pb-3">
<div> <a href="{{ route('roles.create')}}" class="btn btn-success width-lg">Create</a>
<div id="table-roles"></div> </div>
<div id="table-roles"></div>
</div>
</div> </div>
</div> </div>

View File

@@ -9,11 +9,13 @@
@include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'General']) @include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'General'])
<div class="row"> <div class="row">
<div class="d-flex justify-content-end pb-3"> <div class="card w-full">
<a href="{{ route('general.create') }}" class="btn btn-success width-lg">Create</a> <div class="card-body">
</div> <div class="d-flex justify-content-end pb-3">
<div> <a href="{{ route('general.create') }}" class="btn btn-success width-lg">Create</a>
<div id="general-setting-table"></div> </div>
<div id="general-setting-table"></div>
</div>
</div> </div>
</div> </div>

View File

@@ -9,12 +9,16 @@
@include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Syncronize']) @include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Syncronize'])
<div class="row"> <div class="row">
<div class="col-md-12 col-xl-12 d-flex justify-content-end"> <div class="card w-full">
<button type="button" class="btn btn-success" style="margin-right: 20px;" id="btn-sync-submit-google-sheet">Sync Google Sheet</button> <div class="card-body">
<button type="button" class="btn btn-success" id="btn-sync-submit">Sync SIMBG</button> <div class="col-md-12 col-xl-12 d-flex justify-content-end">
</div> <button type="button" class="btn btn-success" style="margin-right: 20px;" id="btn-sync-submit-google-sheet">Sync Google Sheet</button>
<div> <button type="button" class="btn btn-success" id="btn-sync-submit">Sync SIMBG</button>
<div id="table-import-datasources"></div> </div>
<div>
<div id="table-import-datasources"></div>
</div>
</div>
</div> </div>
</div> </div>

View File

@@ -54,6 +54,8 @@ export default defineConfig({
"resources/js/tables/common-table.js", "resources/js/tables/common-table.js",
"resources/js/menus/index.js", "resources/js/menus/index.js",
"resources/js/roles/index.js", "resources/js/roles/index.js",
"resources/js/roles/create.js",
"resources/js/roles/update.js",
"resources/roles/role_menu.js", "resources/roles/role_menu.js",
], ],
refresh: true, refresh: true,