added prop controller

added category api [props]
pull/44/head
A1Gard 3 months ago
parent de88d0ce50
commit bb60c111a6

@ -0,0 +1,141 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Controllers\XController;
use App\Http\Requests\PropSaveRequest;
use App\Models\Access;
use App\Models\Category;
use App\Models\Prop;
use Illuminate\Http\Request;
use App\Helper;
use function App\Helpers\hasCreateRoute;
class PropController extends XController
{
// protected $_MODEL_ = Prop::class;
// protected $SAVE_REQUEST = PropSaveRequest::class;
protected $cols = ['name','label'];
protected $extra_cols = ['id'];
protected $searchable = ['name','label'];
protected $listView = 'admin.props.prop-list';
protected $formView = 'admin.props.prop-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(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**/
}

@ -0,0 +1,72 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\PropCollection;
use App\Models\Category;
use App\Models\Prop;
use Illuminate\Http\Request;
class CategoryController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(Category $category)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Category $category)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Category $category)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Category $category)
{
//
}
public function props(Category $category){
return PropCollection::collection($category->props);
}
}

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PropSaveRequest 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' => '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'
];
}
}

@ -0,0 +1,32 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
class PropCollection extends JsonResource
{
/**
* Transform the resource collection into an array.
*
* @return array<int|string, mixed>
*/
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,
];
}
}

@ -46,4 +46,8 @@ class Category extends Model
return 'slug'; return 'slug';
} }
public function props(){
return $this->belongsToMany(Prop::class);
}
} }

@ -4,8 +4,36 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Translatable\HasTranslations;
class Prop extends Model 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);
}
} }

@ -14,12 +14,12 @@ return new class extends Migration
Schema::create('props', function (Blueprint $table) { Schema::create('props', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('name',90)->unique(); $table->string('name',90)->unique();
$table->string('label'); $table->text('label');
$table->string('width',300)->default('col-md-6'); $table->string('width',300)->default('col-md-6');
$table->boolean('required')->default(false); $table->boolean('required')->default(false);
$table->boolean('searchable')->default(true); $table->boolean('searchable')->default(true);
$table->string('type',60); $table->string('type',60);
$table->string('unit',50)->nullable()->default(''); $table->text('unit')->nullable()->default('');
$table->unsignedInteger('sort')->default(null)->nullable(); $table->unsignedInteger('sort')->default(null)->nullable();
$table->longText('options')->nullable(); $table->longText('options')->nullable();
$table->boolean('priceable')->default(false); $table->boolean('priceable')->default(false);

@ -2,9 +2,6 @@
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\Customer;
use App\Models\Post;
use App\Models\State;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -32,6 +29,7 @@ class DatabaseSeeder extends Seeder
StateSeeder::class, StateSeeder::class,
CustomerSeeder::class, CustomerSeeder::class,
CategorySeeder::class, CategorySeeder::class,
PropSeeder::class,
] ]
); );
} }

@ -2,6 +2,8 @@
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\Category;
use App\Models\Prop;
use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
@ -13,5 +15,74 @@ class PropSeeder extends Seeder
public function run(): void 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());
} }
} }

@ -68,6 +68,9 @@ app.component('slider-data', SliderData);
import AddressInput from "./components/AddressInput.vue"; import AddressInput from "./components/AddressInput.vue";
app.component('address-input', AddressInput); 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 * 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,159 @@
<template>
<div id="prop-type-input">
<div class="row">
<div class="col-md-8 px-0 mt-2">
<select :id="xid" :class="getClass" v-model="val" :name="xname" :required="isRequired">
<option value=""> {{ xtitle }}</option>
<option v-for="t in types"
:value="t"
:selected="t == val">
{{ t }}
</option>
</select>
</div>
<div class="col-md-4 px-0 ps-lg-2 ps-xl-2 ps-xxl-2 mt-2" v-if="val == 'select' || val == 'multi' || val == 'singlemulti' || val =='color'">
<button type="button" class="btn btn-outline-info w-100" @click="addItem">
<i class="ri-add-line"></i>
</button>
</div>
</div>
<div id="extra-data" v-if="val == 'select' || val == 'multi' || val == 'singlemulti' || val =='color'">
<div v-for="(item,i) in items" class="row pb-1 mb-0">
<div class="col-md-6 mt-2">
<input type="text" placeholder="Title" class="form-control" v-model="item.title">
</div>
<div class="col-md-5 mt-2">
<div v-if="val == 'color'">
<input type="color" class="form-control" style="height: 37px" v-model="item.value">
</div>
<div v-else-if="val == 'select' || val == 'multi' || val == 'singlemulti'">
<input type="text" placeholder="Value" class="form-control" v-model="item.value">
</div>
</div>
<div class="col-md-1 text-center mt-2">
<button type="button" @click="remItem(i)" class="btn btn-danger w-100">
<i class="ri-close-line"></i>
</button>
</div>
</div>
</div>
<input type="hidden" :name="xoptionname" :value="jsonItem">
</div>
</template>
<script>
export default {
name: "prop-type-input",
components: {},
data: () => {
return {
val: null,
items: [],
}
},
emits: ['update:modelValue'],
props: {
types: {
required: true,
type: Array,
},
modelValue: {
type: [Number, String],
default: 'nop',
},
xname: {
default: "",
type: String,
},
xoptionname:{
default: 'options',
},
xtitle: {
default: "",
type: String,
},
xvalue: {
default: "",
type: String,
},
xoptions: {
default:[],
},
xid: {
default: "",
type: String,
},
customClass: {
default: "",
type: String,
},
err: {
default: false,
type: Boolean,
},
isRequired:{
default: false,
type: Boolean,
},
},
mounted() {
// console.log(this.types);
if (!isNaN(this.modelValue)) {
this.val = this.modelValue;
} else {
this.val = this.xvalue;
}
this.items = this.xoptions;
// console.log(this.val);
},
computed: {
getClass: function () {
if (this.err == true || (typeof this.err == 'String' && this.err.trim() == '1')) {
return 'form-control is-invalid ' + this.customClass;
}
return 'form-control ' + this.customClass;
},
jsonItem(){
let itms = [];
for( const i of this.items) {
if (i.value != '' && i.title != ''){
itms.push(i);
}
}
// if (itms.length == 0){
// return null;
// }
return JSON.stringify(itms);
}
},
methods: {
addItem(){
let value = '';
if (this.val == 'color'){
value = '#ff0000';
}
this.items.push({
title : '',
value: value
});
},
remItem(i){
if (!confirm('Are you sure?')){ // WIP: translate
return false;
}
this.items.splice(i,1);
}
}
}
</script>
<style scoped>
#prop-type-input {
}
</style>

@ -120,7 +120,7 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
}); });
uploadingImages.addEventListener('dblclick', (e) => { uploadingImages?.addEventListener('dblclick', (e) => {
const imageIndex = e.target.closest('.image-index'); const imageIndex = e.target.closest('.image-index');
if (imageIndex) { if (imageIndex) {
document.querySelectorAll('.indexed').forEach(el => el.classList.remove('indexed')); document.querySelectorAll('.indexed').forEach(el => el.classList.remove('indexed'));

@ -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')
<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>
<li>
{{__("Recommends")}}
</li>
</ul>
</div>
<div class="item-list mb-3">
<h3 class="p-3">
<i class="ri-remixicon-line"></i>
{{__("Icon")}}
</h3>
<div class="p-1 text-center pb-4">
<remix-icon-picker xname="icon" xvalue="{{old('icon',$item->icon??null)}}"></remix-icon-picker>
</div>
</div>
<div class="item-list mb-3">
<h3 class="p-3">
<i class="ri-list-check"></i>
{{__("Categories")}}
</h3>
<div>
<ul class="group-control">
{!!showCatNestedControl($cats,old('cat',isset($item)?$item->categories()->pluck('id')->toArray():[]))!!}
</ul>
</div>
</div>
</div>
<div class="col-lg-9 ps-xl-1 ps-xxl-1">
<div class="general-form ">
<h1>
@if(isset($item))
{{__("Edit prop")}} [{{$item->name}}]
@else
{{__("Add new prop")}}
@endif
</h1>
<div class="row">
<div class="col-md-6 mt-3">
<div class="form-group">
<label for="name">{{__("Name")}}:</label>
<input type="text" name="name" class="form-control" id="name" pattern="[a-z]+"
placeholder="{{__("Name")}}" required
value="{{old('name',$item->name??null)}}">
</div>
</div>
<div class="col-md-6 mt-3">
<div class="form-group">
<label for="label">{{__("Label")}}:</label>
<input type="text" name="label" class="form-control" id="label" required placeholder="{{__("Label")}}"
value="{{old('label',$item->label??null)}}">
</div>
</div>
<div class="col-md-6 mt-3">
<div class="form-group">
<label for="width">{{__("Width")}}:</label>
<input type="text" placeholder="{{__("Width")}}" name="width" class="form-control" id="width" required
value="{{old('width',$item->width??'col-md-12')}}">
</div>
</div>
<div class="col-md-6 mt-3">
<div class="form-group">
<label for="required">{{__("Required")}}:</label>
<select name="required" id="required" class="form-control" required>
<option
value="0" {{ old('required',$item->required??null) == '0' ? 'selected' : '' }} > {{__("Not required")}}
</option>
>
<option
value="1" {{ old('required',$item->required??null) == '1' ? 'selected' : '' }} > {{__("Required")}}</option>
</select>
</div>
</div>
<div class="col-md-4 mt-3">
<div class="form-group">
<label for="searchable">{{__("Searchable")}}:</label>
<select name="searchable" id="searchable" class="form-control" required>
<option
value="0" {{ old('searchable',$item->searchable??null) == '0' ? 'selected' : '' }} > {{__("not searchable")}}
</option>
>
<option
value="1" {{ old('searchable',$item->searchable??null) == '1' ? 'selected' : '' }} > {{__("Searchable")}}
</option>
</select>
</div>
</div>
<div class="col-md-4 mt-3">
<div class="form-group">
<label for="unit">{{__("Unit")}}:</label>
<input type="text" placeholder="{{__("Unit")}}" name="unit" class="form-control" id="unit"
value="{{old('unit',$item->unit??null)}}">
</div>
</div>
<div class="col-md-4 mt-3 py-1">
<div class="form-group mt-4">
<div class="form-check form-switch">
<input class="form-check-input" name="priceable" @if (old('priceable',$item->priceable??0) != 0)
checked
@endif type="checkbox" id="priceable">
<label class="form-check-label" for="priceable"> {{__("Is effective price?")}}</label>
</div>
</div>
</div>
<div class="col-md-12 mt-3">
<label id="type">
{{__("Type")}}
</label>
<props-type-input
xtitle="Select type"
:is-required="true"
:xid="type"
xname="type"
:types='@json(\App\Models\Prop::$prop_types)'
@if(isset($item))
:xoptions='{!! old('options',$item->options) !!}'
@else
:xoptions='{{old('options')}}'
@endif
xvalue="{{old('type',$item->type??'')}}"
></props-type-input>
</div>
<div class="col-md-12">
<label> &nbsp;</label>
<input name="" type="submit" class="btn btn-primary mt-2" value="{{__('Save')}}"/>
</div>
</div>
</div>
</div>
</div>
@endsection

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

@ -52,7 +52,7 @@
</a> </a>
</li> </li>
<li> <li>
<a> <a href="{{route('admin.prop.index')}}">
<i class="ri-file-list-3-fill"></i> <i class="ri-file-list-3-fill"></i>
{{__("Properties meta")}} {{__("Properties meta")}}
</a> </a>

@ -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('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('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');
}); });

@ -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::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::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( Route::prefix('sliders')->name('slider.')->group(
function () { function () {
Route::get('', [\App\Http\Controllers\Admin\SliderController::class, 'index'])->name('index'); Route::get('', [\App\Http\Controllers\Admin\SliderController::class, 'index'])->name('index');

Loading…
Cancel
Save