diff --git a/app/Http/Controllers/Admin/PropController.php b/app/Http/Controllers/Admin/PropController.php new file mode 100644 index 0000000..fb5c002 --- /dev/null +++ b/app/Http/Controllers/Admin/PropController.php @@ -0,0 +1,141 @@ + + ['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(Prop::class, PropSaveRequest::class); + } + + /** + * @param $prop Prop + * @param $request PropSaveRequest + * @return Prop + */ + public function save($prop, $request) + { + +// dd($request->all()); + $prop->name = $request->input('name'); + $prop->type = $request->input('type'); + $prop->required = $request->input('required'); + $prop->searchable = $request->input('searchable'); + $prop->width = $request->input('width'); + $prop->label = $request->input('label'); + $prop->unit = $request->input('unit'); + $prop->priceable = $request->has('priceable'); + $prop->icon = $request->input('icon'); + + + $data = []; + if (($request->has('options')) && $request->input('options') != null && $request->input('options') != ''){ + $data = $request->input('options'); + } + $prop->options = $data; + $prop->save(); + $prop->categories()->sync($request->input('cat')); + return $prop; + + } + + + /** + * Show the form for creating a new resource. + */ + public function create() + { + // + $cats = Category::all(['id','name','parent_id']); + return view($this->formView,compact('cats')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Prop $item) + { + // + $cats = Category::all(['id','name','parent_id']); + return view($this->formView, compact('item','cats')); + } + + 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(Prop $item) + { + return parent::delete($item); + } + + + public function update(Request $request, Prop $item) + { + return $this->bringUp($request, $item); + } + + /**restore*/ + public function restore($item) + { + return parent::restoreing(Prop::withTrashed()->where('id', $item)->first()); + } + /*restore**/ +} diff --git a/app/Http/Controllers/Api/CategoryController.php b/app/Http/Controllers/Api/CategoryController.php new file mode 100644 index 0000000..9dac6e6 --- /dev/null +++ b/app/Http/Controllers/Api/CategoryController.php @@ -0,0 +1,72 @@ +props); + } +} diff --git a/app/Http/Requests/PropSaveRequest.php b/app/Http/Requests/PropSaveRequest.php new file mode 100644 index 0000000..93eead4 --- /dev/null +++ b/app/Http/Requests/PropSaveRequest.php @@ -0,0 +1,34 @@ +check() ; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + 'name' => 'string|required|min:2|max:90', + 'label' => 'string|required|min:2|max:90', +// 'category' => 'required|exists:cats,id', + 'icon'=> 'nullable|string', + 'type' => 'string|min:4' + ]; + } +} diff --git a/app/Http/Resources/PropCollection.php b/app/Http/Resources/PropCollection.php new file mode 100644 index 0000000..c33006c --- /dev/null +++ b/app/Http/Resources/PropCollection.php @@ -0,0 +1,32 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'label' => $this->label, + 'searchable' => (bool) $this->searchable, + 'priceable'=> (bool) $this->priceable, + 'unit' => $this->unit, + 'required' => $this->required, + 'width' => $this->width, + 'icon' => $this->icon, + 'dataList' => $this->dataz, + 'optionList' => $this->optionz, + ]; + } +} diff --git a/app/Models/Category.php b/app/Models/Category.php index c531ad0..a6baf37 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -46,4 +46,8 @@ class Category extends Model return 'slug'; } + public function props(){ + return $this->belongsToMany(Prop::class); + } + } diff --git a/app/Models/Prop.php b/app/Models/Prop.php index df7cf7f..644b9d6 100644 --- a/app/Models/Prop.php +++ b/app/Models/Prop.php @@ -4,8 +4,36 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; +use Spatie\Translatable\HasTranslations; class Prop extends Model { - use HasFactory; + + use HasFactory,HasTranslations,SoftDeletes; + public $translatable = ['label','unit']; + + protected $casts = [ + 'dataz', + 'optionz' + ]; + + public static $prop_types = ['text','number','checkbox','color','select','multi','singlemulti']; + + public function categories() + { + return $this->belongsToMany(Category::class); + } + + public function getDatazAttribute(){ + $result = []; + foreach (json_decode($this->options) as $item) { + $result[$item->title] = $item->value; + } + + return $result; + } + public function getOptionzAttribute(){ + return json_decode($this->options); + } } diff --git a/database/migrations/2024_05_07_130324_create_props_table.php b/database/migrations/2024_05_07_130324_create_props_table.php index d6291c7..51ec3fb 100644 --- a/database/migrations/2024_05_07_130324_create_props_table.php +++ b/database/migrations/2024_05_07_130324_create_props_table.php @@ -14,12 +14,12 @@ return new class extends Migration Schema::create('props', function (Blueprint $table) { $table->id(); $table->string('name',90)->unique(); - $table->string('label'); + $table->text('label'); $table->string('width',300)->default('col-md-6'); $table->boolean('required')->default(false); $table->boolean('searchable')->default(true); $table->string('type',60); - $table->string('unit',50)->nullable()->default(''); + $table->text('unit')->nullable()->default(''); $table->unsignedInteger('sort')->default(null)->nullable(); $table->longText('options')->nullable(); $table->boolean('priceable')->default(false); diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 60e312c..8eb5a55 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,9 +2,6 @@ namespace Database\Seeders; -use App\Models\Customer; -use App\Models\Post; -use App\Models\State; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Storage; @@ -32,6 +29,7 @@ class DatabaseSeeder extends Seeder StateSeeder::class, CustomerSeeder::class, CategorySeeder::class, + PropSeeder::class, ] ); } diff --git a/database/seeders/PropSeeder.php b/database/seeders/PropSeeder.php index 3a5e219..14cf787 100644 --- a/database/seeders/PropSeeder.php +++ b/database/seeders/PropSeeder.php @@ -2,6 +2,8 @@ namespace Database\Seeders; +use App\Models\Category; +use App\Models\Prop; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; @@ -13,5 +15,74 @@ class PropSeeder extends Seeder public function run(): void { // + + Prop::factory()->create([ + 'label' => 'رنگ', + 'name'=>'color', + 'type'=>'color', + 'options' => '[{"title":"black","value":"#000000"},{"title":"white","value":"#ffffff"},{"title":"rose gold","value":"#be9289"},{"title":"silver","value":"#c0c0c0"},{"title":"gold","value":"#d4af37"}]', + 'searchable'=> 0, + 'priceable' => 1 + ]); + Prop::factory()->create([ + 'label' => 'گارانتی', + 'name'=>'warranty', + 'type'=>'select', + 'options' => '[{"title":"no warranty","value":"-1"},{"title":"Rayan","value":"1"},{"title":"Arian","value":"3"},{"title":"Avajang","value":"4"},{"title":"Sazgar Aragham","value":"5"}]', + 'searchable'=> 0, + 'priceable' => 1 + ]); + Prop::factory()->create([ + 'label' => 'شبکه ارتباطی', + 'name'=>'net', + 'type'=>'multi', + 'options' => '[{"title":"2G","value":"2g"},{"title":"3G","value":"3g"},{"title":"4G","value":"4g"},{"title":"5G","value":"5g"}]', + 'searchable'=> 1, + 'priceable' => 0 + ]); + Prop::factory()->create([ + 'label' => 'حافظه داخلی', + 'name'=>'hdd', + 'type'=>'select', + 'options' => '[{"title":"16 Gig","value":"16"},{"title":"32 Gig","value":"32"},{"title":"64 gig","value":"64"},{"title":"128 Gig","value":"128"},{"title":"256 G","value":"256"}]', + 'searchable'=> 1, + 'priceable' => 1 + ]); + Prop::factory()->create([ + 'label' => 'دوربین جلو', + 'name'=>'fcamera', + 'type'=>'text', + 'options' => '[]', + 'searchable'=> 0, + 'priceable' => 0 + ]); + Prop::factory()->create([ + 'label' => 'فناوری صفحه‌نمایش', + 'name'=>'lcd', + 'type'=>'text', + 'options' =>'[]', + 'searchable'=> 0, + 'priceable' => 0 + ]); + Prop::factory()->create([ + 'label' => 'تعداد سیم کارت', + 'name'=>'sim', + 'type'=>'number', + 'options' =>'[]', + 'searchable'=> 1, + 'priceable' => 0 + ]); + Prop::factory()->create([ + 'label' => 'پشتیبانی از کارت حافظه', + 'name'=>'sdcard', + 'type'=>'checkbox', + 'options' =>'[]', + 'searchable'=> 1, + 'priceable' => 0 + ]); + + Category::where('id',1)->first()->props()->sync(Prop::pluck('id')->toArray()); + Category::where('id',2)->first()->props()->sync(Prop::pluck('id')->toArray()); + } } diff --git a/resources/js/app.js b/resources/js/app.js index db54e23..10da649 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -68,6 +68,9 @@ app.component('slider-data', SliderData); import AddressInput from "./components/AddressInput.vue"; app.component('address-input', AddressInput); + +import PropTypeInput from "./components/PropTypeInput.vue"; +app.component('props-type-input', PropTypeInput); /** * The following block of code may be used to automatically register your * Vue components. It will recursively scan this directory for the Vue diff --git a/resources/js/components/PropTypeInput.vue b/resources/js/components/PropTypeInput.vue new file mode 100644 index 0000000..aa20cdb --- /dev/null +++ b/resources/js/components/PropTypeInput.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/resources/js/panel/product-upload-controller.js b/resources/js/panel/product-upload-controller.js index d7c54f2..a70fef1 100644 --- a/resources/js/panel/product-upload-controller.js +++ b/resources/js/panel/product-upload-controller.js @@ -120,7 +120,7 @@ document.addEventListener('DOMContentLoaded', () => { } }); }); - uploadingImages.addEventListener('dblclick', (e) => { + uploadingImages?.addEventListener('dblclick', (e) => { const imageIndex = e.target.closest('.image-index'); if (imageIndex) { document.querySelectorAll('.indexed').forEach(el => el.classList.remove('indexed')); diff --git a/resources/views/admin/props/prop-form.blade.php b/resources/views/admin/props/prop-form.blade.php new file mode 100644 index 0000000..756e088 --- /dev/null +++ b/resources/views/admin/props/prop-form.blade.php @@ -0,0 +1,154 @@ +@extends('admin.templates.panel-form-template') +@section('title') + @if(isset($item)) + {{__("Edit prop")}} [{{$item->name}}] + @else + {{__("Add new prop")}} + @endif - +@endsection +@section('form') + +
+
+ + @include('components.err') +
+

+ + {{__("Tips")}} +

+
    +
  • + {{__("Recommends")}} +
  • +
+
+
+

+ + {{__("Icon")}} +

+
+ +
+
+
+

+ + {{__("Categories")}} +

+
+
    + {!!showCatNestedControl($cats,old('cat',isset($item)?$item->categories()->pluck('id')->toArray():[]))!!} +
+
+
+ +
+
+
+ +

+ @if(isset($item)) + {{__("Edit prop")}} [{{$item->name}}] + @else + {{__("Add new prop")}} + @endif +

+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ +
+ priceable??0) != 0) + checked + @endif type="checkbox" id="priceable"> + +
+
+
+
+ + options) !!}' + @else + :xoptions='{{old('options')}}' + @endif + xvalue="{{old('type',$item->type??'')}}" + > +
+
+ + +
+
+ +
+
+
+@endsection diff --git a/resources/views/admin/props/prop-list.blade.php b/resources/views/admin/props/prop-list.blade.php new file mode 100644 index 0000000..f7a703d --- /dev/null +++ b/resources/views/admin/props/prop-list.blade.php @@ -0,0 +1,15 @@ +@extends('admin.templates.panel-list-template') + +@section('list-title') + + {{__("Props list")}} +@endsection +@section('title') + {{__("Props list")}} - +@endsection +@section('filter') + {{-- Other filters --}} +@endsection +@section('bulk') + {{-- --}} +@endsection diff --git a/resources/views/components/panel-side-navbar.blade.php b/resources/views/components/panel-side-navbar.blade.php index 14431c8..0e023ad 100644 --- a/resources/views/components/panel-side-navbar.blade.php +++ b/resources/views/components/panel-side-navbar.blade.php @@ -52,7 +52,7 @@
  • - + {{__("Properties meta")}} diff --git a/routes/api.php b/routes/api.php index 87fb233..fbc65e5 100644 --- a/routes/api.php +++ b/routes/api.php @@ -35,4 +35,5 @@ Route::prefix('v1')->name('v1.')->group( Route::get('states', [\App\Http\Controllers\Api\StateController::class,'index'])->name('state.index'); Route::get('state/{state}', [\App\Http\Controllers\Api\StateController::class,'show'])->name('state.show'); + Route::get('category/props/{category}', [\App\Http\Controllers\Api\CategoryController::class,'props'])->name('category.prop'); }); diff --git a/routes/web.php b/routes/web.php index 64a8459..93279bb 100644 --- a/routes/web.php +++ b/routes/web.php @@ -142,6 +142,18 @@ Route::prefix(config('app.panel.prefix'))->name('admin.')->group( Route::get('delete/{item}', [\App\Http\Controllers\Admin\ProductController::class, 'destroy'])->name('destroy'); Route::post('bulk', [\App\Http\Controllers\Admin\ProductController::class, "bulk"])->name('bulk'); }); + Route::prefix('props')->name('prop.')->group( + function () { + Route::get('', [\App\Http\Controllers\Admin\PropController::class, 'index'])->name('index'); + Route::get('create', [\App\Http\Controllers\Admin\PropController::class, 'create'])->name('create'); + Route::post('store', [\App\Http\Controllers\Admin\PropController::class, 'store'])->name('store'); + Route::get('show/{item}', [\App\Http\Controllers\Admin\PropController::class, 'show'])->name('show'); + Route::post('title/update', [\App\Http\Controllers\Admin\PropController::class, 'updateTitle'])->name('title'); + Route::get('edit/{item}', [\App\Http\Controllers\Admin\PropController::class, 'edit'])->name('edit'); + Route::post('update/{item}', [\App\Http\Controllers\Admin\PropController::class, 'update'])->name('update'); + Route::get('delete/{item}', [\App\Http\Controllers\Admin\PropController::class, 'destroy'])->name('destroy'); + Route::post('bulk', [\App\Http\Controllers\Admin\PropController::class, "bulk"])->name('bulk'); + }); Route::prefix('sliders')->name('slider.')->group( function () { Route::get('', [\App\Http\Controllers\Admin\SliderController::class, 'index'])->name('index');