added menu controller [WIP: sort]

added menu item input vue component
added v-model(s) & name support to MorphSelector
pull/44/head
A1Gard 4 months ago
parent 5661e73e11
commit f1dec8db9f

@ -0,0 +1,147 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Controllers\XController;
use App\Http\Requests\MenuSaveRequest;
use App\Models\Access;
use App\Models\Item;
use App\Models\Menu;
use Illuminate\Http\Request;
use App\Helper;
use function App\Helpers\hasCreateRoute;
class MenuController extends XController
{
// protected $_MODEL_ = Menu::class;
// protected $SAVE_REQUEST = MenuSaveRequest::class;
protected $cols = ['name'];
protected $extra_cols = ['id'];
protected $searchable = ['name'];
protected $listView = 'admin.menus.menu-list';
protected $formView = 'admin.menus.menu-form';
protected $buttons = [
'edit' =>
['title' => "Edit", 'class' => 'btn-outline-primary', 'icon' => 'ri-edit-2-line'],
// 'show' =>
// ['title' => "Detail", 'class' => 'btn-outline-light', 'icon' => 'ri-eye-line'],
'destroy' =>
['title' => "Remove", 'class' => 'btn-outline-danger delete-confirm', 'icon' => 'ri-close-line'],
];
public function __construct()
{
parent::__construct(Menu::class, MenuSaveRequest::class);
}
/**
* @param $menu Menu
* @param $request MenuSaveRequest
* @return Menu
*/
public function save($menu, $request)
{
$menu->name = $request->input('name');
if ($menu->user_id == null){
$menu->user_id = auth()->user()->id;
}
$menu->save();
$items = json_decode($request->input('items','[]'));
foreach ($items as $item) {
if ($item->id == null){
$i = new Item();
}else{
$i = Item::whereId($item->id)->first();
}
$i->user_id = auth()->user()->id;
$i->menu_id = $menu->id;
$i->meta = $item->meta??null;
$i->sort = $item->sort;
$i->parent = $item->parent;
$i->kind = $item->kind;
$i->title = $item->title;
$i->menuable_id = $item->menuable_id??null;
$i->menuable_type = $item->menuable_type??null;
$i->save();
}
Item::whereIn('id',json_decode($request->input('removed','[]')))->delete();
return $menu;
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
return view($this->formView);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Menu $item)
{
//
return view($this->formView, compact('item'));
}
public function bulk(Request $request)
{
// dd($request->all());
$data = explode('.', $request->input('action'));
$action = $data[0];
$ids = $request->input('id');
switch ($action) {
case 'delete':
$msg = __(':COUNT items deleted successfully', ['COUNT' => count($ids)]);
$this->_MODEL_::destroy($ids);
break;
/**restore*/
case 'restore':
$msg = __(':COUNT items restored successfully', ['COUNT' => count($ids)]);
foreach ($ids as $id) {
$this->_MODEL_::withTrashed()->find($id)->restore();
}
break;
/*restore**/
default:
$msg = __('Unknown bulk action : :ACTION', ["ACTION" => $action]);
}
return $this->do_bulk($msg, $action, $ids);
}
public function destroy(Menu $item)
{
return parent::delete($item);
}
public function update(Request $request, Menu $item)
{
return $this->bringUp($request, $item);
}
/**restore*/
public function restore($item)
{
return parent::restoreing(Menu::withTrashed()->where('id', $item)->first());
}
/*restore**/
}

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MenuSaveRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required','string','max:255','min:2'],
];
}
}

@ -10,6 +10,8 @@ class Menu extends Model
{ {
use HasFactory,SoftDeletes; use HasFactory,SoftDeletes;
public static $mrohps = [Product::class,Post::class,Group::class,
Category::class,Clip::class,Gallery::class];
public function items() public function items()
{ {
return $this->hasMany(Item::class); return $this->hasMany(Item::class);

@ -113,6 +113,9 @@ app.component('area-designer', AreaDesginer);
import Latlng from "./components/latlng.vue"; import Latlng from "./components/latlng.vue";
app.component('lat-lng', Latlng); app.component('lat-lng', Latlng);
import MenuItemInput from "./components/MenuItemInput.vue";
app.component('menu-item-input', MenuItemInput);
/** /**
* The following block of code may be used to automatically register your * The following block of code may be used to automatically register your
* Vue components. It will recursively scan this directory for the Vue * Vue components. It will recursively scan this directory for the Vue

@ -0,0 +1,164 @@
<template>
<div class="menu-item">
<div v-for="(item,i) in currentItems" :key="i">
<div class="card">
<div class="card-header">
Menu item {{ i + 1 }}
</div>
<div class="card-body">
<div class="row p-0">
<div class="col-md-11">
<div>
<!-- WIP translate-->
<label :for="`kind-${i}`">
Kind
</label>
<select v-model="item.kind" class="form-control" :id="`kind-${i}`">
<option :value="null"> Kind</option>
<option :value="kind" v-for="kind in kinds"> {{ kind }}</option>
</select>
</div>
<div v-if="item.kind == 'module'">
<label :for="`title2-${i}`">
Title
</label>
<input type="text" v-model="item.title" placeholder="Title" class="form-control"
:id="`title2-${i}`">
<morph-selector
xname-type=""
xname-id=""
:morph-search-link="this.morphSearchLink"
:morphs="morphs"
:xlang="this.xlang"
v-model:model-id="item.menuable_id"
v-model:model-type="item.menuable_type"
>
</morph-selector>
</div>
<div v-else-if="item.kind == 'direct'">
<div class="row pt-2">
<div class="col-md p-0 pe-2">
<label :for="`title1-${i}`">
Title
</label>
<input type="text" v-model="item.title" placeholder="Title" class="form-control"
:id="`title1-${i}`">
</div>
<div class="col-md p-0">
<label :for="`meta-${i}`">
Link
</label>
<input type="text" v-model="item.meta" placeholder="Link" class="form-control"
:id="`meta-${i}`">
</div>
</div>
</div>
<div v-else class="py-2">
<div class="alert bg-danger">
Please select kind
</div>
</div>
</div>
<div class="col-md d-flex justify-content-center align-items-center">
<button type="button" class="btn btn-primary" @click="remItem(i)">
<i class="ri-close-line"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-primary my-3" @click="addItem">
<i class="ri-add-line"></i>
</button>
<input type="hidden" name="items" :value="JSON.stringify(this.currentItems)">
<input type="hidden" name="removed" :value="JSON.stringify(this.removed)">
</div>
</template>
<script>
import MorphSelector from "./MorphSelector.vue";
export default {
name: "menu-item",
components: {MorphSelector},
data: () => {
return {
currentItems: [],
removed: [],
kinds: [
'direct',
'module'
],
}
},
props: {
morphs: {
default: [],
},
xlang: {
default: null,
},
morphSearchLink: {
default: null,
},
items: {
default: []
},
menuId: {
required: true,
}
},
mounted() {
let tmp = [];
for (const i in this.items) {
if (typeof (this.items[i].title) != 'undefined') {
tmp[i] = {...this.items[i]};
tmp[i].title = this.items[i].title[this.xlang];
}
}
this.currentItems = tmp;
},
computed: {},
methods: {
addItem() {
this.currentItems.push({
id: null,
title: null,
menuable_id: null,
menuable_type: null,
kind: 'direct',
sort: this.currentItems.length,
parent: null,
menu_id: this.menuId,
});
},
remItem(i) {
if (this.currentItems[i].id != null) {
this.removed.push(this.currentItems[i].id);
}
this.currentItems.splice(i, 1)
}
}
}
</script>
<style scoped>
.menu-item {
padding-right: 1rem;
padding-left: 1rem;
}
.menu-item .card{
margin-bottom: 1rem;
}
.menu-item .card-body{
background: #ffffff11;
}
</style>

@ -1,16 +1,17 @@
<template> <template>
<div id="morph-selector"> <div class="morph-selector">
<div v-if="this.morph != null && this.id != null"> <div v-if="this.morph != null && this.id != null">
{{ humanReadableMorph(morph) }}: [{{ id }}] {{ humanReadableMorph(morph) }}: [{{ id }}]
</div> </div>
<select class="form-control" v-model="morph" @change="updateList"> <select class="form-control mt-2" v-model="morph" @change="updateList">
<option :value="null"> Module</option>
<option v-for="m in morphs" :value="m"> {{ humanReadableMorph(m) }}</option> <option v-for="m in morphs" :value="m"> {{ humanReadableMorph(m) }}</option>
</select> </select>
<label id="q" class="mt-2"> <label id="q" class="mt-2">
Search Search
<!-- WIP : translate--> <!-- WIP : translate-->
</label> </label>
<input @input="updateList" type="text" id="q" v-model="q" class="form-control"> <input @input="updateList" type="text" id="q" v-model="q" class="form-control" placeholder="search">
<div v-if="all.length > 0"> <div v-if="all.length > 0">
<ul class="list-group my-2"> <ul class="list-group my-2">
@ -38,8 +39,8 @@
</ul> </ul>
</div> </div>
<div v-if="this.morph != null && this.id != null"> <div v-if="this.morph != null && this.id != null">
<input type="hidden" :value="morph" name="attachable_type"> <input type="hidden" :value="morph" :name="xnameType">
<input type="hidden" :value="id" name="attachable_id"> <input type="hidden" :value="id" :name="xnameId">
</div> </div>
</div> </div>
</template> </template>
@ -54,10 +55,24 @@ export default {
q: '', q: '',
all: [], all: [],
id: null, id: null,
} }
}, },
props: { props: {
emits: ['update:modelType', 'update:modelId'],
modelType: {
default: 'noting',
type: String,
},
modelId: {
default: 'noting',
type: String,
},
xnameType: {
default: 'attachable_type'
},
xnameId: {
default: 'attachable_id'
},
morphs: { morphs: {
default: [], default: [],
}, },
@ -75,12 +90,18 @@ export default {
} }
}, },
mounted() { mounted() {
if (this.modelType == 'noting' || this.modelType == 'noting') {
if (this.xmorph != null && this.xmorph != 'null' && this.xmorph != '') { if (this.xmorph != null && this.xmorph != 'null' && this.xmorph != '') {
this.morph = this.xmorph; this.morph = this.xmorph;
} }
if (this.xid != null && this.xid != 'null' && this.xid != '') { if (this.xid != null && this.xid != 'null' && this.xid != '') {
this.id = parseInt(this.xid); this.id = parseInt(this.xid);
} }
} else {
this.morph = this.modelType;
this.id = this.modelId;
}
}, },
computed: {}, computed: {},
methods: { methods: {
@ -103,13 +124,27 @@ export default {
humanReadableMorph(morph) { humanReadableMorph(morph) {
const tmp = morph.split('\\'); const tmp = morph.split('\\');
return tmp[tmp.length - 1]; return tmp[tmp.length - 1];
},
},
watch: {
morph(newValue) {
if (this.modelType != 'noting') {
this.$emit('update:modelType', newValue);
} }
},
id(newValue) {
if (this.modelId != 'noting' && newValue != null) {
this.$emit('update:modelId', newValue.toString());
}
},
} }
} }
</script> </script>
<style scoped> <style scoped>
#morph-selector { .morph-selector {
} }

@ -0,0 +1,80 @@
@extends('admin.templates.panel-form-template')
@section('title')
@if(isset($item))
{{__("Edit menu")}} [{{$item->name}}]
@else
{{__("Add new menu")}}
@endif -
@endsection
@section('form')
<div class="row">
<div class="col-lg-3">
@include('components.err')
<div class="item-list mb-3">
<h3 class="p-3">
<i class="ri-message-3-line"></i>
{{__("Tips")}}
</h3>
<ul>
@if(isset($item))
<li>
{{__("You can add item after create menu")}}
</li>
@else
<li>
{{__("Added items view depends on theme part")}}
</li>
@endif
</ul>
</div>
</div>
<div class="col-lg-9 ps-xl-1 ps-xxl-1">
<div class="general-form ">
<h1>
@if(isset($item))
{{__("Edit menu")}} [{{$item->name}}]
@else
{{__("Add new menu")}}
@endif
</h1>
<div class="row">
<div class="col-md-12 mt-3">
<div class="form-group">
<label for="name">
{{__('Name')}}
</label>
<input name="name" type="text"
id="name"
class="form-control @error('name') is-invalid @enderror"
placeholder="{{__('Name')}}"
value="{{old('name',$item->name??null)}}"/>
</div>
</div>
<div class="col-md-12">
<label> &nbsp;</label>
<input name="" type="submit" class="btn btn-primary mt-2" value="{{__('Save')}}"/>
</div>
</div>
@if(isset($item))
<h4 class="px-4">
{{__("Menu items")}}
</h4>
<menu-item-input
:morphs='@json(\App\Models\Menu::$mrohps)'
morph-search-link="{{route('v1.morph.search')}}"
xlang="{{config('app.locale')}}"
:items='@json($item->items)'
menu-id="{{$item->id}}"
></menu-item-input>
@endif
</div>
</div>
</div>
@endsection

@ -0,0 +1,15 @@
@extends('admin.templates.panel-list-template')
@section('list-title')
<i class="ri-user-3-line"></i>
{{__("Menus list")}}
@endsection
@section('title')
{{__("Menus list")}} -
@endsection
@section('filter')
{{-- Other filters --}}
@endsection
@section('bulk')
{{-- <option value="-"> - </option> --}}
@endsection

@ -117,7 +117,7 @@
</a> </a>
<ul id="themes"> <ul id="themes">
<li> <li>
<a href=""> <a href="{{route('admin.menu.index')}}">
<i class="ri-list-check"></i> <i class="ri-list-check"></i>
{{__("Menus")}} {{__("Menus")}}
</a> </a>

@ -1,10 +1,10 @@
@yield('custom-foot') @yield('custom-foot')
<input type="hidden" id="api-display-url" value="{{route('v1.visitor.display')}}"> <input type="hidden" id="api-display-url" value="{{route('v1.visitor.display')}}">
@if(auth()->check() && auth()->user()->hasRole('developer') && !request()->has('ediable')) {{--@if(auth()->check() && auth()->user()->hasRole('developer') && !request()->has('ediable'))--}}
<a id="do-edit" href="?ediable"> {{--<a id="do-edit" href="?ediable">--}}
<i class="ri-settings-2-line"></i> {{-- <i class="ri-settings-2-line"></i>--}}
</a> {{--</a>--}}
@endif {{--@endif--}}
</body> </body>
</html> </html>

@ -201,6 +201,19 @@ Route::prefix(config('app.panel.prefix'))->name('admin.')->group(
Route::post('bulk', [\App\Http\Controllers\Admin\CustomerController::class, "bulk"])->name('bulk'); Route::post('bulk', [\App\Http\Controllers\Admin\CustomerController::class, "bulk"])->name('bulk');
Route::get('trashed', [\App\Http\Controllers\Admin\CustomerController::class, "trashed"])->name('trashed'); Route::get('trashed', [\App\Http\Controllers\Admin\CustomerController::class, "trashed"])->name('trashed');
}); });
Route::prefix('menus')->name('menu.')->group(
function () {
Route::get('', [\App\Http\Controllers\Admin\MenuController::class, 'index'])->name('index');
Route::get('create', [\App\Http\Controllers\Admin\MenuController::class, 'create'])->name('create');
Route::post('store', [\App\Http\Controllers\Admin\MenuController::class, 'store'])->name('store');
Route::get('show/{item}', [\App\Http\Controllers\Admin\MenuController::class, 'show'])->name('show');
Route::get('edit/{item}', [\App\Http\Controllers\Admin\MenuController::class, 'edit'])->name('edit');
Route::post('update/{item}', [\App\Http\Controllers\Admin\MenuController::class, 'update'])->name('update');
Route::get('delete/{item}', [\App\Http\Controllers\Admin\MenuController::class, 'destroy'])->name('destroy');
Route::get('restore/{item}', [\App\Http\Controllers\Admin\MenuController::class, 'restore'])->name('restore');
Route::post('bulk', [\App\Http\Controllers\Admin\MenuController::class, "bulk"])->name('bulk');
Route::get('trashed', [\App\Http\Controllers\Admin\MenuController::class, "trashed"])->name('trashed');
});
Route::prefix('states')->name('state.')->group( Route::prefix('states')->name('state.')->group(
function () { function () {
Route::get('', [\App\Http\Controllers\Admin\StateController::class, 'index'])->name('index'); Route::get('', [\App\Http\Controllers\Admin\StateController::class, 'index'])->name('index');

Loading…
Cancel
Save