added fast category edit ux feature

master
A1Gard 2 days ago
parent 89f80a50ce
commit d1ef32bb79

@ -19,10 +19,10 @@ class ProductController extends XController
// protected $_MODEL_ = Product::class; // protected $_MODEL_ = Product::class;
// protected $SAVE_REQUEST = ProductSaveRequest::class; // protected $SAVE_REQUEST = ProductSaveRequest::class;
protected $cols = ['name','category_id','view','sell','status']; protected $cols = ['name', 'category_id', 'view', 'sell', 'status'];
protected $extra_cols = ['id','slug','image_index']; protected $extra_cols = ['id', 'slug', 'image_index'];
protected $searchable = ['name','slug','description','excerpt','sku','table']; protected $searchable = ['name', 'slug', 'description', 'excerpt', 'sku', 'table'];
protected $listView = 'admin.products.product-list'; protected $listView = 'admin.products.product-list';
protected $formView = 'admin.products.product-form'; protected $formView = 'admin.products.product-form';
@ -34,6 +34,8 @@ class ProductController extends XController
['title' => "Detail", 'class' => 'btn-outline-light', 'icon' => 'ri-eye-line'], ['title' => "Detail", 'class' => 'btn-outline-light', 'icon' => 'ri-eye-line'],
'destroy' => 'destroy' =>
['title' => "Remove", 'class' => 'btn-outline-danger delete-confirm', 'icon' => 'ri-close-line'], ['title' => "Remove", 'class' => 'btn-outline-danger delete-confirm', 'icon' => 'ri-close-line'],
'category' =>
['title' => "Edit category", 'class' => 'btn-outline-light edit-category-btn', 'icon' => 'ri-list-check-3'],
]; ];
@ -52,18 +54,18 @@ class ProductController extends XController
// dd($request->all()); // dd($request->all());
$product->name = $request->input('name'); $product->name = $request->input('name');
$product->slug = $this->getSlug($product,'slug','name'); $product->slug = $this->getSlug($product, 'slug', 'name');
$product->table = $request->input('table'); $product->table = $request->input('table');
$product->description = $request->input('desc'); $product->description = $request->input('desc');
$product->excerpt = $request->input('excerpt'); $product->excerpt = $request->input('excerpt');
$product->keyword = $request->input('keyword'); $product->keyword = $request->input('keyword');
$product->stock_status = $request->input('stock_status'); $product->stock_status = $request->input('stock_status');
$product->price = $request->input('price',0); $product->price = $request->input('price', 0);
$product->buy_price = $request->input('buy_price',0); $product->buy_price = $request->input('buy_price', 0);
if (!$request->has('quantity')) { if (!$request->has('quantity')) {
$product->price = $request->input('price',0); $product->price = $request->input('price', 0);
$product->stock_quantity = $request->input('stock_quantity'); $product->stock_quantity = $request->input('stock_quantity');
} }
$product->average_rating = $request->input('average_rating', 0); $product->average_rating = $request->input('average_rating', 0);
@ -74,17 +76,17 @@ class ProductController extends XController
$product->virtual = $request->input('virtual', false); $product->virtual = $request->input('virtual', false);
$product->downloadable = $request->input('downloadable', false); $product->downloadable = $request->input('downloadable', false);
$product->category_id = $request->input('category_id'); $product->category_id = $request->input('category_id');
$product->image_index = $request->input('index_image',0); $product->image_index = $request->input('index_image', 0);
$product->user_id = auth()->id(); $product->user_id = auth()->id();
$product->status = $request->input('status'); $product->status = $request->input('status');
$tags = array_filter(explode(',,', $request->input('tags'))); $tags = array_filter(explode(',,', $request->input('tags')));
if ($request->has('canonical') && trim($request->input('canonical')) != ''){ if ($request->has('canonical') && trim($request->input('canonical')) != '') {
$product->canonical = $request->input('canonical'); $product->canonical = $request->input('canonical');
} }
$product->save(); $product->save();
$product->categories()->sync($request->input('cat')); $product->categories()->sync($request->input('cat'));
if (count($tags) > 0){ if (count($tags) > 0) {
$product->syncTags($tags); $product->syncTags($tags);
} }
@ -103,17 +105,17 @@ class ProductController extends XController
if ($request->has('meta')) { if ($request->has('meta')) {
// dd($request->input('meta')); // dd($request->input('meta'));
$product->syncMeta(json_decode($request->get('meta','[]'),true)); $product->syncMeta(json_decode($request->get('meta', '[]'), true));
} }
$toRemoveQ = $product->quantities()->pluck('id')->toArray(); $toRemoveQ = $product->quantities()->pluck('id')->toArray();
if ($request->has('q')){ if ($request->has('q')) {
$qz = json_decode($request->input('q')); $qz = json_decode($request->input('q'));
foreach ($qz as $qi){ foreach ($qz as $qi) {
if ($qi->id == null){ if ($qi->id == null) {
$q = new Quantity(); $q = new Quantity();
}else{ } else {
$q = Quantity::whereId($qi->id)->first(); $q = Quantity::whereId($qi->id)->first();
unset($toRemoveQ[array_search($q->id, $toRemoveQ) ]); // remove for to remove IDz unset($toRemoveQ[array_search($q->id, $toRemoveQ)]); // remove for to remove IDz
} }
$q->image = $qi->image; $q->image = $qi->image;
$q->count = $qi->count; $q->count = $qi->count;
@ -122,9 +124,9 @@ class ProductController extends XController
$q->data = json_encode($qi->data); $q->data = json_encode($qi->data);
$q->save(); $q->save();
} }
$product->quantities()->whereIn('id',$toRemoveQ)->delete(); $product->quantities()->whereIn('id', $toRemoveQ)->delete();
if ($product->quantities()->count() > 0){ if ($product->quantities()->count() > 0) {
$product->stock_quantity = $product->quantities()->sum('count'); $product->stock_quantity = $product->quantities()->sum('count');
$product->price = $product->quantities()->min('price'); $product->price = $product->quantities()->min('price');
} }
@ -143,8 +145,8 @@ class ProductController extends XController
public function create() public function create()
{ {
// //
$cats = Category::all(['id','name','parent_id']); $cats = Category::all(['id', 'name', 'parent_id']);
return view($this->formView,compact('cats')); return view($this->formView, compact('cats'));
} }
/** /**
@ -154,8 +156,8 @@ class ProductController extends XController
{ {
// //
$cats = Category::all(['id','name','parent_id']); $cats = Category::all(['id', 'name', 'parent_id']);
return view($this->formView, compact('item','cats')); return view($this->formView, compact('item', 'cats'));
} }
public function bulk(Request $request) public function bulk(Request $request)
@ -211,4 +213,27 @@ class ProductController extends XController
} }
/*restore**/ /*restore**/
/**
* @param $id Product's id
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Foundation\Application|\Illuminate\View\View
*/
public function categoryEdit($id)
{
$product = Product::find($id);
$cats = Category::all(['id', 'name', 'parent_id']);
return view('admin.products.category-edit', compact('product', 'cats'));
}
public function categorySave(Product $item, Request $request)
{
$item->categories()->sync($request->input('cat'));
logAdmin(__METHOD__, __CLASS__, $item->id);
if ($request->ajax()) {
return ['OK' => true, 'message' => __('Categories saved successfully')];
} else {
return redirect()->back()->with(['message' => __('Categories saved successfully')]);
}
}
} }

@ -31,6 +31,7 @@ import './panel/sotable-controller.js';
import './panel/prototypes.js'; import './panel/prototypes.js';
import './panel/panel-window-loader.js'; import './panel/panel-window-loader.js';
import './panel/responsive-control.js'; import './panel/responsive-control.js';
import './panel/fast-edit.js';
// import './panel/seo-analyzer.js'; // import './panel/seo-analyzer.js';
// chartjs.defaults.defaultFontFamily = "Vazir"; // chartjs.defaults.defaultFontFamily = "Vazir";

@ -0,0 +1,59 @@
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.edit-category-btn')?.forEach(function (el) {
el.setAttribute('href', '#edit-category');
el.addEventListener('click', function (e) {
e.preventDefault();
let id = this.closest('tr').querySelector('input.chkbox').getAttribute('value');
const url = document.querySelector('#category-edit-url').value + id;
document.querySelector('#iframe-modal iframe').setAttribute('src', url);
document.querySelector('#iframe-modal').style.display = 'block';
});
});
document.querySelector('#iframe-modal')?.addEventListener('click', function (e) {
if (e.target == this) {
this.style.display = 'none';
}
});
document.querySelector('#categories-save-btn')?.addEventListener('click', function (e) {
e.preventDefault();
const url = document.querySelector('#ajax-sync-form').getAttribute('action');
// Serialize the form data
const formData = new FormData();
const checkboxes = document.querySelectorAll('input[type="checkbox"]:checked');
checkboxes.forEach(checkbox => {
formData.append('cat[]', checkbox.value);
});
// Optional: log serialized data for debugging
for (const [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
// Get the URL from the form's action attribute
// Make the AJAX POST request using Axios
axios.post(url, formData)
.then(response => {
// Handle success
if (response.data.OK == true){
$toast.success(response.data.message);
}else{
$toast.error(response.data.error);
}
})
.catch(error => {
// Handle error
$toast.error( error);
});
});
});

@ -474,3 +474,34 @@ a.btn,a.action-btn,a.circle-btn{
} }
} }
.nested-ul{
list-style: none;
ul{
list-style: none;
}
input[type='checkbox']{
margin: 0 1rem;
}
}
#iframe-modal{
background: #00000011;
backdrop-filter: blur(10px);
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
z-index: 999;
padding-top: 5vh;
display: none;
iframe{
border: 0;
height: 90vh;
width: 100%;
border-radius: 1rem;
}
}

@ -30,6 +30,10 @@
padding: 1rem; padding: 1rem;
} }
} }
.alert{
margin-bottom: 1rem;
}
} }
.table-list { .table-list {

@ -0,0 +1,27 @@
@include('components.panel-header')
<div id="panel-preloader">
<div class="loader"></div>
</div>
<input type="hidden" id="panel-dir" @if(langIsRTL(config('app.locale'))) value="rtl" @else value="ltr" @endif>
<form action="{{route('admin.product.category-save',$product->slug)}}" method="post" id="ajax-sync-form">
@csrf
<div class="container pt-4" >
@include('components.err')
<h5>
{{__("Categories")}} [{{$product->name}}]
</h5>
<ul class="nested-ul">
{!!showCatNestedControl($cats,old('cat',isset($product)?$product->categories()->pluck('id')->toArray():[]))!!}
</ul>
</div>
<button class="action-btn circle-btn"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-custom-class="custom-tooltip"
data-bs-title="{{__("Save")}}"
id="categories-save-btn"
>
<i class="ri-save-2-line"></i>
</button>
</form>
@include('components.panel-footer')

@ -10,6 +10,12 @@
@section('filter') @section('filter')
{{-- Other filters --}} {{-- Other filters --}}
<h2> <h2>
<input type="hidden" id="category-edit-url" value="{{route('admin.product.category-edit','')}}/">
<div id="iframe-modal">
<div class="container">
<iframe href="#"></iframe>
</div>
</div>
<i class="ri-book-3-line"></i> <i class="ri-book-3-line"></i>
{{__("Category")}}: {{__("Category")}}:
</h2> </h2>

@ -301,6 +301,9 @@ Route::prefix(config('app.panel.prefix'))->name('admin.')->group(
Route::get('restore/{item}', [\App\Http\Controllers\Admin\ProductController::class, 'restore'])->name('restore'); Route::get('restore/{item}', [\App\Http\Controllers\Admin\ProductController::class, 'restore'])->name('restore');
Route::post('bulk', [\App\Http\Controllers\Admin\ProductController::class, "bulk"])->name('bulk'); Route::post('bulk', [\App\Http\Controllers\Admin\ProductController::class, "bulk"])->name('bulk');
Route::get('trashed', [\App\Http\Controllers\Admin\ProductController::class, "trashed"])->name('trashed'); Route::get('trashed', [\App\Http\Controllers\Admin\ProductController::class, "trashed"])->name('trashed');
Route::get('category/edit/{id}', [\App\Http\Controllers\Admin\ProductController::class, 'categoryEdit'])->name('category-edit');
Route::post('category/save/{item}', [\App\Http\Controllers\Admin\ProductController::class, 'categorySave'])->name('category-save');
}); });
Route::prefix('props')->name('prop.')->group( Route::prefix('props')->name('prop.')->group(
function () { function () {

Loading…
Cancel
Save