save quantities

fixed some bugs for update and edit page
pull/44/head
A1Gard 3 months ago
parent dec15afcc3
commit 2cc3475431

@ -393,6 +393,11 @@ function getModelLink($modelable_type, $modelable_id)
} }
} }
/**
* fix action in log
* @param $act
* @return string
*/
function getAction($act) function getAction($act)
{ {
$r = explode('::', $act); $r = explode('::', $act);
@ -400,6 +405,10 @@ function getAction($act)
} }
/**
* ge all admin routes array
* @return array
*/
function getAdminRoutes() function getAdminRoutes()
{ {
$routes = []; $routes = [];
@ -414,3 +423,20 @@ function getAdminRoutes()
return $routes; return $routes;
} }
/**
* get model with all custom attributes
* @param $model \Illuminate\Database\Eloquent\Model
* @return void
*/
function modelWithCustomAttrs($model){
$data = $model->toArray();
$attrs = $model->getMutatedAttributes();
$attrs = array_diff($attrs,['translations']);
foreach ($attrs as $attr) {
$data[$attr] = $model->getAttribute($attr);
}
return $data;
}

@ -8,6 +8,7 @@ use App\Http\Requests\ProductSaveRequest;
use App\Models\Access; use App\Models\Access;
use App\Models\Category; use App\Models\Category;
use App\Models\Product; use App\Models\Product;
use App\Models\Quantity;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Helper; use App\Helper;
use function App\Helpers\hasCreateRoute; use function App\Helpers\hasCreateRoute;
@ -26,7 +27,6 @@ class ProductController extends XController
protected $listView = 'admin.products.product-list'; protected $listView = 'admin.products.product-list';
protected $formView = 'admin.products.product-form'; protected $formView = 'admin.products.product-form';
protected $buttons = [ protected $buttons = [
'edit' => 'edit' =>
['title' => "Edit", 'class' => 'btn-outline-primary', 'icon' => 'ri-edit-2-line'], ['title' => "Edit", 'class' => 'btn-outline-primary', 'icon' => 'ri-edit-2-line'],
@ -99,6 +99,30 @@ class ProductController extends XController
// 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();
if ($request->has('q')){
$qz = json_decode($request->input('q'));
foreach ($qz as $qi){
if ($qi->id == null){
$q = new Quantity();
}else{
$q = Quantity::whereId($qi->id)->first();
unset($toRemoveQ[array_search($q->id, $toRemoveQ) ]); // remove for to remove IDz
}
$q->image = $qi->image;
$q->count = $qi->count;
$q->price = $qi->price;
$q->product_id = $product->id;
$q->data = json_encode($qi->data);
$q->save();
}
$product->quantities()->whereIn('id',$toRemoveQ)->delete();
$product->stock_quantity = $product->quantities()->sum('count');
$product->price = $product->quantities()->min('price');
$product->save();
}
return $product; return $product;
@ -121,6 +145,7 @@ class ProductController extends XController
public function edit(Product $item) public function edit(Product $item)
{ {
// //
$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'));
} }
@ -168,5 +193,6 @@ class ProductController extends XController
{ {
return parent::restoreing(Product::withTrashed()->where('id', $item)->first()); return parent::restoreing(Product::withTrashed()->where('id', $item)->first());
} }
/*restore**/ /*restore**/
} }

@ -122,6 +122,7 @@ abstract class XController extends Controller
if ($request->ajax()) { if ($request->ajax()) {
return ['OK' => true, "message" => __('As you wished created successfully'), return ['OK' => true, "message" => __('As you wished created successfully'),
"id" => $item->id, "id" => $item->id,
"data" => modelWithCustomAttrs($item) ,
'url' => getRoute('edit', $item->{$item->getRouteKeyName()})]; 'url' => getRoute('edit', $item->{$item->getRouteKeyName()})];
} else { } else {
return redirect(getRoute('edit', $item->{$item->getRouteKeyName()})) return redirect(getRoute('edit', $item->{$item->getRouteKeyName()}))
@ -150,7 +151,9 @@ abstract class XController extends Controller
logAdmin(__METHOD__, $this->_MODEL_, $item->id); logAdmin(__METHOD__, $this->_MODEL_, $item->id);
if ($request->ajax()) { if ($request->ajax()) {
return ['OK' => true, "message" => __('As you wished updated successfully'), "id" => $item->id]; return ['OK' => true,
"data" => modelWithCustomAttrs($item) ,
"message" => __('As you wished updated successfully'), "id" => $item->id];
} else { } else {
return redirect(getRoute('edit', $item->{$item->getRouteKeyName()})) return redirect(getRoute('edit', $item->{$item->getRouteKeyName()}))
->with(['message' => __('As you wished updated successfully')]); ->with(['message' => __('As you wished updated successfully')]);

@ -16,6 +16,30 @@ class Product extends Model implements HasMedia
{ {
use HasFactory, SoftDeletes, InteractsWithMedia, HasTranslations, HasTags, Metable; use HasFactory, SoftDeletes, InteractsWithMedia, HasTranslations, HasTags, Metable;
protected $casts = [
'qz' => 'array',
'qidz' => 'array'
];
protected $guarded = [];
public function getQzAttribute(){
$result = [];
foreach ($this->quantities as $q) {
if ($q->count > 0){
$q->data = json_decode($q->data);
$result[] = $q;
}
}
return $result;
}
public function getQidzAttribute(){
return $this->quantities()->pluck('id')->toArray();
}
public static $stock_status = ['IN_STOCK', 'OUT_STOCK', 'BACK_ORDER']; public static $stock_status = ['IN_STOCK', 'OUT_STOCK', 'BACK_ORDER'];
public $translatable = ['name', 'excerpt', 'description','table']; public $translatable = ['name', 'excerpt', 'description','table'];
@ -83,7 +107,7 @@ class Product extends Model implements HasMedia
public function quantities() public function quantities()
{ {
return $this->hasMany(Quantity::class, 'product_id'); return $this->hasMany(Quantity::class);
} }
public function discounts() public function discounts()
@ -148,4 +172,5 @@ class Product extends Model implements HasMedia
} }
} }

@ -104,3 +104,4 @@ app.mount('#app');
window.app = app; window.app = app;
window.$toast = $toast; window.$toast = $toast;
window.store = store;

@ -1,15 +1,24 @@
<template> <template>
<div id="meta-input"> <div id="meta-input">
<div id="img-modal" @click.self="modal = false" v-if="modal" class="d-flex align-items-center justify-content-center">
<div class="container">
<div class="row">
<div :class="`col-md-4 `+(j == quantities[qOnEdit].image?'selected-img':'')" v-for="(img,j) in imgz" >
<img :src="img.original_url" @click="changeImgIndex(j)" alt="{{img.id}}" class="img-index">
</div>
</div>
</div>
</div>
<div class="row"> <div class="row">
<div v-for="prop in properties" :class="prop.width"> <div v-for="prop in properties" :class="prop.width">
<label for="prop.name" v-if="prop.type != 'checkbox'"> <label for="prop.name" v-if="prop.type != 'checkbox'">
{{prop.label}} {{ prop.label }}
<!-- [{{prop.type}}]--> <!-- [{{prop.type}}]-->
</label> </label>
<div v-else class="mt-2"> <div v-else class="mt-2">
<br> <br>
</div> </div>
<template v-if="meta[prop.name] != undefined"> <div v-if="meta[prop.name] != undefined" class="position-relative">
<template v-if="prop.type == 'text'"> <template v-if="prop.type == 'text'">
<input type="text" :id="prop.name" v-model="meta[prop.name]" class="form-control"> <input type="text" :id="prop.name" v-model="meta[prop.name]" class="form-control">
</template> </template>
@ -18,27 +27,118 @@
</template> </template>
<template v-if="prop.type == 'checkbox'"> <template v-if="prop.type == 'checkbox'">
<div class="form-check form-switch"> <div class="form-check form-switch">
<input class="form-check-input" v-model="meta[prop.name]" type="checkbox" role="switch" :id="prop.name"> <input class="form-check-input" v-model="meta[prop.name]" type="checkbox" role="switch"
<label class="form-check-label" for="flexSwitchCheckDefault">{{prop.label}}</label> :id="prop.name">
<label class="form-check-label" for="flexSwitchCheckDefault">{{ prop.label }}</label>
</div> </div>
</template> </template>
<template v-if="prop.type == 'color'"> <template v-if="prop.type == 'color'">
<select :id="prop.name" class="form-control color" v-model="meta[prop.name]"> <select :id="prop.name" class="form-control color" v-model="meta[prop.name]">
<option v-for="op in prop.optionList" :style="`background: ${op.value} ;`" :value="op.value"> {{op.title}} </option> <option v-for="op in prop.optionList" :style="`background: ${op.value} ;`"
:value="op.value"> {{ op.title }}
</option>
</select> </select>
<div class="sq" :style="`background: ${meta[prop.name]} ;`"></div>
</template> </template>
<template v-if="prop.type == 'select' || prop.type == 'singemulti'"> <template v-if="prop.type == 'select' || prop.type == 'singemulti'">
<select :id="prop.name" class="form-control color" v-model="meta[prop.name]"> <select :id="prop.name" class="form-control color" v-model="meta[prop.name]">
<option v-for="op in prop.optionList" :value="op.value"> {{op.title}} </option> <option v-for="op in prop.optionList" :value="op.value"> {{ op.title }}</option>
</select> </select>
</template> </template>
<template v-if="prop.type == 'multi'"> <template v-if="prop.type == 'multi'">
<searchable-multi-select :items="prop.optionList" value-field="value" v-model="meta[prop.name]"></searchable-multi-select> <searchable-multi-select :items="prop.optionList" value-field="value"
v-model="meta[prop.name]"></searchable-multi-select>
</template> </template>
</div>
</div>
</div>
<div v-if="hasPriceable && productId != null" class="mt-4">
<h4>
Quantities:
<!-- WIP: transalte-->
</h4>
<button type="button" class="btn btn-light w-100 mb-2" @click="addQ">
<i class="ri-add-line"></i>
</button>
<!-- qz: {{ quantitiez }}, qs: {{ quantities }}-->
<div class="row mt-1" v-for="(q,n) in quantities">
<template v-for="prop in properties">
<div v-if="prop.priceable" class="col-md">
<label for="prop.name" v-if="prop.type != 'checkbox'">
{{ prop.label }}
<!-- [{{prop.type}}]-->
</label>
<div v-if="meta[prop.name] != undefined" class="position-relative">
<template v-if="prop.type == 'text'">
<input type="text" :id="prop.name" v-model="q.data[prop.name]" class="form-control">
</template>
<template v-if="prop.type == 'number'">
<input type="number" :id="prop.name" v-model="q.data[prop.name]" class="form-control">
</template>
<template v-if="prop.type == 'checkbox'">
<div class="form-check form-switch">
<input class="form-check-input" v-model="q.data[prop.name]" type="checkbox"
role="switch"
:id="prop.name">
<label class="form-check-label" for="flexSwitchCheckDefault">{{
prop.label
}}</label>
</div>
</template>
<template v-if="prop.type == 'color'">
<select :id="prop.name" class="form-control color" v-model="q.data[prop.name]">
<option v-for="op in prop.optionList" :style="`background: ${op.value} ;`"
:value="op.value"> {{ op.title }}
</option>
</select>
<div class="sq" :style="`background: ${q.data[prop.name]} ;`"></div>
</template>
<template v-if="prop.type == 'select' || prop.type == 'singemulti'">
<select :id="prop.name" class="form-control color" v-model="q.data[prop.name]">
<option v-for="op in prop.optionList" :value="op.value"> {{ op.title }}</option>
</select>
</template>
<template v-if="prop.type == 'multi'">
<searchable-multi-select xname="" :items="prop.optionList" value-field="value"
v-model="q.data[prop.name]"></searchable-multi-select>
</template>
</div>
</div>
</template> </template>
<div class="col-md">
<label :for="`qid-${n}`">
Count: <!-- WIP: transalte-->
</label>
<input type="number" placeholder="Count" :id="`qid-${n}`" min="0" v-model="q.count" class="form-control">
</div>
<div class="col-md">
<label :for="`prc-${n}`">
Price: <!-- WIP: transalte-->
</label>
<currency-input :xid="`qid-${n}`" xtitle="Price" v-model="q.price"></currency-input>
</div>
<div class="col-md" v-if="imgz.length > 0">
<label :for="`img-${n}`">
image: <!-- WIP: transalte-->
</label>
<button type="button" class="btn btn-outline-info d-block w-100" @click="showModal(n)">
<i class="ri-image-2-line"></i>
</button>
</div>
<div class="col-md-1">
<br>
<button type="button" class="btn btn-outline-danger d-block w-100" @click="remQ(n)">
<i class="ri-close-line"></i>
</button>
</div>
</div> </div>
</div> </div>
<!-- {{quantities}}-->
<input type="hidden" name="meta" :value="JSON.stringify(meta)"> <input type="hidden" name="meta" :value="JSON.stringify(meta)">
<input type="hidden" name="q" :value="JSON.stringify(quantities)">
</div> </div>
</template> </template>
@ -46,27 +146,60 @@
import {mapState} from "vuex"; import {mapState} from "vuex";
import searchableMultiSelect from "./SearchableMultiSelect.vue"; import searchableMultiSelect from "./SearchableMultiSelect.vue";
import CurrencyInput from "./CurrencyInput.vue";
function arraysEqual(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
const sortedArr1 = arr1.slice().sort();
const sortedArr2 = arr2.slice().sort();
return sortedArr1.every((value, index) => value === sortedArr2[index]);
}
export default { export default {
name: "meta-input", name: "meta-input",
components: { components: {
searchableMultiSelect searchableMultiSelect,
CurrencyInput
}, },
data: () => { data: () => {
return { return {
properties: [], properties: [],
meta:{} meta: {},
hasPriceable: false,
quantities: [],
qOnEdit: 0,
modal: false,
lastCat: null,
} }
}, },
props: { props: {
propsApiLink:{ imgz:{
default: []
},
propsApiLink: {
required: true, required: true,
}, },
metaz:{ metaz: {
default: [],
},
quantitiez: {
default: [], default: [],
},
productId: {
default: null,
} }
} , },
mounted() { mounted() {
// this.quantities = this.quantitiez;
for( const q of this.quantitiez) {
q.data = JSON.parse(q.data);
this.quantities.push(q);
}
}, },
computed: { computed: {
category_id: { category_id: {
@ -77,40 +210,98 @@ export default {
this.$store.commit('UPDATE_CATEGORY', value) this.$store.commit('UPDATE_CATEGORY', value)
} }
}, },
qsid: {
get() {
return this.$store.state.quantities;
},
set(value) {
// this.$store.commit('UPDATE_CATEGORY', value)
}
},
qid(){
let r = [];
for( const q of this.quantities) {
r.push(q.id);
}
return r;
}
}, },
methods: { methods: {
async updateProps(){
showModal(i){
console.log('ii',i);
this.qOnEdit = i;
this.modal = true;
},
changeImgIndex(i){
console.log('jjj',i);
this.quantities[this.qOnEdit].image = i;
},
remQ(i){
this.quantities.splice(i,1);
},
addQ() {
let data = {
id: null,
product_id: this.productId,
image: null,
price: 0,
count: 0,
data: {},
};
for (const prop of this.properties) {
// check priceable
if (prop.priceable) {
data.data[prop.name] = '';
}
}
this.quantities.push(data);
},
async updateProps() {
try { try {
const url = this.propsApiLink + this.category_id; const url = this.propsApiLink + this.category_id;
let resp = await axios.get(url); let resp = await axios.get(url);
this.properties = resp.data.data; this.properties = resp.data.data;
// added don't have // added don't have
for( const prop of this.properties) { for (const prop of this.properties) {
if (this.meta[prop.name] == undefined){ // check priceable
if (prop.type == 'multi'){ if (prop.priceable) {
this.hasPriceable = true;
}
if (this.meta[prop.name] == undefined) {
if (prop.type == 'multi') {
this.meta[prop.name] = []; this.meta[prop.name] = [];
}else{ } else {
this.meta[prop.name] = ''; this.meta[prop.name] = '';
} }
} }
} }
// update by old meta data // update by old meta data
for( const meta in this.metaz) { for (const meta in this.metaz) {
this.meta[meta] = this.metaz[meta]; this.meta[meta] = this.metaz[meta];
} }
} catch (e) {
} catch(e) {
window.$toast.error(e.message); window.$toast.error(e.message);
} }
}, },
}, },
watch: { watch: {
category_id: function() { category_id: function (old,n) {
this.updateProps(); console.log(old,n,'x');
// if (this.lastCat != this.category_id){
// this.lastCat = this.category_id;
this.updateProps();
// }
},
qsid: function () {
if (!arraysEqual(this.qid,this.qsid)){
window.location.href = window.redirect;
}
} }
} }
} }
@ -120,7 +311,41 @@ export default {
#meta-input { #meta-input {
} }
.color option{
.color option {
}
.sq {
width: 37px;
height: 37px;
background: transparent;
border: 1px solid black;
position: absolute;
inset-inline-end: 0;
top: 0;
border-radius: 4px;
}
#img-modal{
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #00000033;
backdrop-filter: blur(5px);
z-index: 10;
//display: none;
}
.img-index{
width: 100%;
height: 25vh;
min-height: 200px;
object-fit: cover;
}
.selected-img{
background: darkred;
} }
</style> </style>

@ -3,16 +3,23 @@ import Vuex from 'vuex';
export default new Vuex.Store({ export default new Vuex.Store({
state: { state: {
category: '' category: '',
quantities: [],
}, },
mutations: { mutations: {
UPDATE_CATEGORY(state, payload) { UPDATE_CATEGORY(state, payload) {
state.category = payload; state.category = payload;
} },
UPDATE_QUANTITIES(state, payload) {
state.quantities = payload;
},
}, },
actions:{ actions:{
updateCategory(context,cat){ updateCategory(context,cat){
context.commit('UPDATE_CATEGORY',cat); context.commit('UPDATE_CATEGORY',cat);
} },
updateQuantities(context,idz){
context.commit('UPDATE_QUANTITIES',idz);
},
} }
}); });

@ -98,10 +98,10 @@ document.addEventListener('DOMContentLoaded', () => {
if (res.data.link !== undefined) { if (res.data.link !== undefined) {
this.setAttribute('action', res.data.link); this.setAttribute('action', res.data.link);
} }
window.redirect = currentEditLink + res.data.data.slug;
this.setAttribute('action', currentUpdateLink + res.data.data.slug)
$toast.info(res.data.message); $toast.info(res.data.message);
if (document.querySelector('#price-amount').value.trim() !== '') { window.store.dispatch('updateQuantities',res.data.data.qidz);
window.location.reload();
}
} }
} }
}).catch(error => { }).catch(error => {

@ -165,3 +165,9 @@
<br> <br>
@yield('out-of-form') @yield('out-of-form')
@endsection @endsection
@section('js-content')
<script>
var currentEditLink = '{{route('admin.product.edit','')}}/';
var currentUpdateLink = '{{route('admin.product.update','')}}/';
</script>
@endsection

@ -1,6 +1,12 @@
<meta-input <meta-input
props-api-link="{{route('v1.category.prop','')}}/" props-api-link="{{route('v1.category.prop','')}}/"
@if(isset($item)) @if(isset($item))
:metaz='@json($item->getAllMeta())' :metaz='@json($item->getAllMeta())'
:quantitiez='@json($item->quantities)'
product-id="{{$item->id}}"
:imgz='@json($item->getMedia()->toArray())'
@endif @endif
></meta-input> ></meta-input>

Loading…
Cancel
Save