mirror of https://github.com/4xmen/xshop.git
added product controller [WIP: discount & meta & quantity]
parent
62debcbe6d
commit
de88d0ce50
@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\XController;
|
||||
use App\Http\Requests\ProductSaveRequest;
|
||||
use App\Models\Access;
|
||||
use App\Models\Category;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Helper;
|
||||
use function App\Helpers\hasCreateRoute;
|
||||
|
||||
class ProductController extends XController
|
||||
{
|
||||
|
||||
// protected $_MODEL_ = Product::class;
|
||||
// protected $SAVE_REQUEST = ProductSaveRequest::class;
|
||||
|
||||
protected $cols = ['name','category_id','view_count','sell_count'];
|
||||
protected $extra_cols = ['id','slug','image_index'];
|
||||
|
||||
protected $searchable = ['name','slug','description','excerpt','sku','table'];
|
||||
|
||||
protected $listView = 'admin.products.product-list';
|
||||
protected $formView = 'admin.products.product-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(Product::class, ProductSaveRequest::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $product Product
|
||||
* @param $request ProductSaveRequest
|
||||
* @return Product
|
||||
*/
|
||||
public function save($product, $request)
|
||||
{
|
||||
|
||||
// dd($request->all());
|
||||
$product->name = $request->input('name');
|
||||
$product->slug = $this->getSlug($product,'slug','title');
|
||||
$product->table = $request->input('table');
|
||||
$product->description = $request->input('desc');
|
||||
$product->excerpt = $request->input('excerpt');
|
||||
$product->stock_status = $request->input('stock_status');
|
||||
if (!$request->has('quantity')) {
|
||||
$product->price = $request->input('price',0);
|
||||
$product->stock_quantity = $request->input('stock_quantity');
|
||||
}
|
||||
$product->average_rating = $request->input('average_rating', 0);
|
||||
$product->average_rating = $request->input('average_rating', 0);
|
||||
$product->rating_count = $request->input('rating_count', 0);
|
||||
$product->on_sale = $request->input('on_sale', 1);
|
||||
$product->sku = $request->input('sku', null);
|
||||
$product->virtual = $request->input('virtual', false);
|
||||
$product->downloadable = $request->input('downloadable', false);
|
||||
$product->category_id = $request->input('category_id');
|
||||
$product->image_index = $request->input('index_image',0);
|
||||
$product->user_id = auth()->id();
|
||||
$product->status = $request->input('status');
|
||||
$tags = array_filter(explode(',,', $request->input('tags')));
|
||||
$product->save();
|
||||
$product->categories()->sync($request->input('cat'));
|
||||
if (count($tags) > 0){
|
||||
$product->syncTags($tags);
|
||||
}
|
||||
foreach ($product->getMedia() as $media) {
|
||||
in_array($media->id, request('medias', [])) ?: $media->delete();
|
||||
}
|
||||
foreach ($request->file('image', []) as $image) {
|
||||
try {
|
||||
$product->addMedia($image)
|
||||
->preservingOriginal() //middle method
|
||||
->toMediaCollection(); //finishing method
|
||||
} catch (FileDoesNotExist $e) {
|
||||
} catch (FileIsTooBig $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return $product;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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(Product $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(Product $item)
|
||||
{
|
||||
return parent::delete($item);
|
||||
}
|
||||
|
||||
|
||||
public function update(Request $request, Product $item)
|
||||
{
|
||||
return $this->bringUp($request, $item);
|
||||
}
|
||||
|
||||
/**restore*/
|
||||
public function restore($item)
|
||||
{
|
||||
return parent::restoreing(Product::withTrashed()->where('id', $item)->first());
|
||||
}
|
||||
/*restore**/
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProductSaveRequest 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', 'min:5', 'max:128', "unique:products,name," . $this->id],
|
||||
'sku' => ['nullable', 'string', 'min:1', 'max:128', "unique:products,sku," . $this->id],
|
||||
'body' => ['nullable', 'string', 'min:5'],
|
||||
'excerpt' => ['required', 'string', 'min:5'],
|
||||
'active' => ['nullable', 'boolean'],
|
||||
'meta' => ['nullable'],
|
||||
'category_id' => ['required', 'exists:categories,id'],
|
||||
'image.*' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048'
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Question extends Model
|
||||
{
|
||||
// use HasFactory;
|
||||
public function product(){
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
public function customer(){
|
||||
return $this->belongsTo(Customer::class);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('questions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->text('body');
|
||||
$table->unsignedBigInteger('customer_id');
|
||||
$table->text('answer')->default(null)->nullable();
|
||||
$table->unsignedBigInteger('product_id');
|
||||
$table->tinyInteger('status')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('product_id')->on('products')
|
||||
->references('id')->onDelete('cascade');
|
||||
$table->foreign('customer_id')->on('customers')
|
||||
->references('id')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('questions');
|
||||
}
|
||||
};
|
@ -0,0 +1,193 @@
|
||||
var isW8 = false;
|
||||
let uploadFormData = [];
|
||||
let xTimer;
|
||||
window.noSubmit = false;
|
||||
|
||||
function previewImage(input, i) {
|
||||
try {
|
||||
const oFReader = new FileReader();
|
||||
oFReader.readAsDataURL(input);
|
||||
oFReader.onload = function (oFREvent) {
|
||||
const img = oFREvent.target.result;
|
||||
const uploadingImages = document.querySelector('#uploading-images');
|
||||
const newDiv = document.createElement('div');
|
||||
newDiv.dataset.id = i;
|
||||
newDiv.className = 'col-xl-3 col-md-4 border p-3';
|
||||
newDiv.innerHTML = `
|
||||
<div class="img-preview" style="background-image: url('${img}')"></div>
|
||||
<div class="btn btn-danger upload-remove-image d-block">
|
||||
<span class="ri-close-line"></span>
|
||||
</div>
|
||||
`;
|
||||
uploadingImages.appendChild(newDiv);
|
||||
};
|
||||
|
||||
if (xTimer !== undefined) {
|
||||
clearTimeout(xTimer);
|
||||
}
|
||||
|
||||
xTimer = setTimeout(() => {
|
||||
document.querySelectorAll('.img-preview').forEach(el => {
|
||||
el.style.height = `${el.offsetWidth}px`;
|
||||
});
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}, 300);
|
||||
} catch (e) {
|
||||
console.error('Error in previewImage:', e);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const uploadingImages = document.querySelector('#uploading-images');
|
||||
const uploadDragDrop = document.querySelector('#upload-drag-drop');
|
||||
const uploadImageSelect = document.querySelector('#upload-image-select');
|
||||
const indexImage = document.querySelector('#index-image');
|
||||
|
||||
document.querySelector('.product-form')?.addEventListener('submit', function(e) {
|
||||
|
||||
e.preventDefault();
|
||||
if (isW8 || window.noSubmit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const formData = new FormData(this);
|
||||
let j = 0;
|
||||
for (const f of uploadFormData) {
|
||||
if (uploadFormData.length == j) {
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
try {
|
||||
if (f.size === undefined) {
|
||||
continue;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
continue;
|
||||
}
|
||||
console.log('x',f);
|
||||
formData.append('image[]', f);
|
||||
}
|
||||
|
||||
const submitButtons = document.querySelectorAll("[type='submit']");
|
||||
submitButtons.forEach(button => {
|
||||
button.disabled = true;
|
||||
button.classList.add('w8');
|
||||
});
|
||||
|
||||
isW8 = true;
|
||||
const url = this.getAttribute('action');
|
||||
|
||||
|
||||
axios({
|
||||
method: 'post',
|
||||
url: url,
|
||||
data: formData,
|
||||
headers: {'Content-Type': 'multipart/form-data'}
|
||||
}).then(res => {
|
||||
submitButtons.forEach(button => {
|
||||
button.disabled = false;
|
||||
button.classList.remove('w8');
|
||||
});
|
||||
isW8 = false;
|
||||
|
||||
if (res.data.OK) {
|
||||
if (res.data.url !== undefined) {
|
||||
window.location.href = res.data.url;
|
||||
} else {
|
||||
if (res.data.link !== undefined) {
|
||||
this.setAttribute('action', res.data.link);
|
||||
}
|
||||
if (document.querySelector('#price-amount').value.trim() !== '') {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
document.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid'));
|
||||
submitButtons.forEach(button => {
|
||||
button.disabled = false;
|
||||
button.classList.remove('w8');
|
||||
});
|
||||
isW8 = false;
|
||||
|
||||
for (let i in error.response.data.errors) {
|
||||
document.getElementById(i)?.classList.add('is-invalid');
|
||||
for (const err of error.response.data.errors[i]) {
|
||||
// alertify.error(err);
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
uploadingImages.addEventListener('dblclick', (e) => {
|
||||
const imageIndex = e.target.closest('.image-index');
|
||||
if (imageIndex) {
|
||||
document.querySelectorAll('.indexed').forEach(el => el.classList.remove('indexed'));
|
||||
imageIndex.classList.add('indexed');
|
||||
indexImage.value = imageIndex.dataset.key;
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.img-preview').forEach(el => {
|
||||
el.style.height = `${el.offsetWidth}px`;
|
||||
});
|
||||
|
||||
uploadDragDrop?.addEventListener('click', () => {
|
||||
uploadImageSelect.click();
|
||||
});
|
||||
|
||||
uploadImageSelect?.addEventListener('change', () => {
|
||||
for (const file of uploadImageSelect.files) {
|
||||
console.log(file);
|
||||
uploadFormData.push(file);
|
||||
previewImage(file, uploadFormData.length);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.upload-remove-image')) {
|
||||
const parentCol = e.target.closest('.col-md-4');
|
||||
const dataId = parentCol.dataset.id;
|
||||
delete uploadFormData[dataId - 1];
|
||||
parentCol.style.transition = 'opacity 400ms';
|
||||
parentCol.style.opacity = '0';
|
||||
setTimeout(() => parentCol.remove(), 400);
|
||||
}
|
||||
});
|
||||
|
||||
uploadDragDrop?.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
uploadDragDrop.classList.add('active');
|
||||
});
|
||||
|
||||
['dragenter', 'dragstart'].forEach(eventName => {
|
||||
uploadDragDrop?.addEventListener(eventName, (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
uploadDragDrop.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
['dragleave', 'dragend'].forEach(eventName => {
|
||||
uploadDragDrop?.addEventListener(eventName, (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
uploadDragDrop.classList.remove('active');
|
||||
});
|
||||
});
|
||||
|
||||
uploadDragDrop?.addEventListener('drop', (e) => {
|
||||
uploadDragDrop.classList.remove('active');
|
||||
if (e.dataTransfer && e.dataTransfer.files.length) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
for (const f of e.dataTransfer.files) {
|
||||
previewImage(f, uploadFormData.length);
|
||||
uploadFormData.push(f);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,59 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const steps = document.querySelectorAll('.steps li');
|
||||
const stepsContainer = document.querySelector('.steps');
|
||||
const stepTabs = document.querySelectorAll('#step-tabs > div');
|
||||
const nextButtons = document.querySelectorAll('.step-next');
|
||||
const prevButtons = document.querySelectorAll('.step-prev');
|
||||
let currentStep = 0;
|
||||
|
||||
function updateProgress(stepIndex) {
|
||||
const progress = (stepIndex + 1) / steps.length * 100;
|
||||
stepsContainer?.style.setProperty('--progress', `${progress}%`);
|
||||
}
|
||||
|
||||
function showStep(stepIndex) {
|
||||
steps.forEach((step, index) => {
|
||||
step.classList.toggle('active', index <= stepIndex);
|
||||
});
|
||||
|
||||
stepTabs.forEach((tab, index) => {
|
||||
if (index === stepIndex) {
|
||||
tab.classList.add('active');
|
||||
setTimeout(() => tab.style.opacity = 1, 0);
|
||||
} else {
|
||||
tab.style.opacity = 0;
|
||||
setTimeout(() => tab.classList.remove('active'), 0);
|
||||
}
|
||||
});
|
||||
|
||||
updateProgress(stepIndex);
|
||||
currentStep = stepIndex;
|
||||
}
|
||||
|
||||
function nextStep() {
|
||||
if (currentStep < steps.length - 1) {
|
||||
showStep(currentStep + 1);
|
||||
}
|
||||
}
|
||||
|
||||
function prevStep() {
|
||||
if (currentStep > 0) {
|
||||
showStep(currentStep - 1);
|
||||
}
|
||||
}
|
||||
|
||||
steps.forEach((step, index) => {
|
||||
step.addEventListener('click', () => showStep(index));
|
||||
});
|
||||
|
||||
nextButtons.forEach(button => {
|
||||
button.addEventListener('click', nextStep);
|
||||
});
|
||||
|
||||
prevButtons.forEach(button => {
|
||||
button.addEventListener('click', prevStep);
|
||||
});
|
||||
|
||||
// Show the first step initially
|
||||
showStep(0);
|
||||
});
|
@ -0,0 +1,113 @@
|
||||
.steps {
|
||||
|
||||
|
||||
// ... other styles ...
|
||||
--progress: 0%;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
list-style-type: none;
|
||||
padding: 1rem 0;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: calc(100% - var(--progress));
|
||||
border-bottom: 1px solid lighten($primary-color-panel,20);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
background: $lighter-color;
|
||||
cursor: pointer;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 65px;
|
||||
height: 65px;
|
||||
|
||||
i {
|
||||
font-size: 35px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: lighten($primary-color-panel,20);
|
||||
background-color: $primary-color-panel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#step-tabs {
|
||||
> div {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
//transition: opacity 0.3s ease-in-out;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-form .card-footer{
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
|
||||
#upload-drag-drop {
|
||||
border: 2px dashed silver;
|
||||
min-height: 150px;
|
||||
padding: 20px 10px;
|
||||
text-align: center;
|
||||
margin: 10px auto;
|
||||
transition: 600ms;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
border-color: $primary-color-panel;
|
||||
}
|
||||
}
|
||||
#upload-drag-drop.active {
|
||||
border-color: $primary-color-panel;
|
||||
}
|
||||
|
||||
.img-preview{
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.img-list{
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 275px;
|
||||
}
|
||||
|
||||
|
||||
.indexed{
|
||||
background: $primary-color-panel;
|
||||
}
|
||||
|
||||
#upload-image-select{
|
||||
display: none;
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title')
|
||||
@if(isset($item))
|
||||
{{__("Edit product")}} [{{$item->id}}]
|
||||
@else
|
||||
{{__("Add new product")}}
|
||||
@endif -
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
||||
@if(hasRoute('create') && isset($item))
|
||||
<a class="action-btn circle-btn"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="custom-tooltip"
|
||||
data-bs-title="{{__("Add another one")}}"
|
||||
href="{{getRoute('create')}}"
|
||||
>
|
||||
<i class="ri-add-line"></i>
|
||||
</a>
|
||||
@else
|
||||
<a class="action-btn circle-btn"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="custom-tooltip"
|
||||
data-bs-title="{{__("Show list")}}"
|
||||
href="{{getRoute('index',[])}}"
|
||||
>
|
||||
<i class="ri-list-view"></i>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<form
|
||||
@if(isset($item))
|
||||
id="product-form-edit"
|
||||
action="{{getRoute('update',$item->{$item->getRouteKeyName()})}}"
|
||||
@else
|
||||
id="product-form-create"
|
||||
action="{{getRoute('store')}}"
|
||||
@endif
|
||||
class="product-form pb-5 pb-5"
|
||||
method="post" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@if(isset($item))
|
||||
<input type="hidden" name="id" value="{{$item->id}}"/>
|
||||
@endif
|
||||
<ul class="steps">
|
||||
<li data-tab="step1"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="custom-tooltip"
|
||||
data-bs-title="{{__("Basic data")}}">
|
||||
<i class="ri-pages-line"></i>
|
||||
</li>
|
||||
<li data-tab="step2"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="custom-tooltip"
|
||||
data-bs-title="{{__("Medias")}}">
|
||||
<i class="ri-image-2-line"></i>
|
||||
</li>
|
||||
<li data-tab="step3"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="custom-tooltip"
|
||||
data-bs-title="{{__("Additional data")}}">
|
||||
<i class="ri-list-check-2"></i>
|
||||
</li>
|
||||
<li data-tab="step4"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
data-bs-custom-class="custom-tooltip"
|
||||
data-bs-title="{{__("Publish")}}">
|
||||
<i class="ri-save-3-line"></i>
|
||||
</li>
|
||||
</ul>
|
||||
<div id="step-tabs">
|
||||
<div id="step1">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{{__("Basic data")}}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@include('admin.products.sub-pages.product-step1')
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button type="submit" class="btn btn-light">
|
||||
{{__("Publish")}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-light step-next">
|
||||
{{__("Next")}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="step2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{{__("Medias")}}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@include('admin.products.sub-pages.product-step2')
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<!-- code -->
|
||||
<button type="submit" class="btn btn-light">
|
||||
{{__("Publish")}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-light step-next">
|
||||
{{__("Next")}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-light step-prev">
|
||||
{{__("Previous")}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="step3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{{__("Additional data")}}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@include('admin.products.sub-pages.product-step3')
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<!-- code -->
|
||||
<button type="submit" class="btn btn-light">
|
||||
{{__("Publish")}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-light step-next">
|
||||
{{__("Next")}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-light step-prev">
|
||||
{{__("Previous")}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="step4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{{__("Publish")}}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@include('admin.products.sub-pages.product-step4')
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<!-- code -->
|
||||
<button type="submit" class="btn btn-light">
|
||||
{{__("Publish")}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-light step-prev">
|
||||
{{__("Previous")}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br>
|
||||
<br>
|
||||
@yield('out-of-form')
|
||||
@endsection
|
@ -0,0 +1,30 @@
|
||||
@extends('admin.templates.panel-list-template')
|
||||
|
||||
@section('list-title')
|
||||
<i class="ri-user-3-line"></i>
|
||||
{{__("Products list")}}
|
||||
@endsection
|
||||
@section('title')
|
||||
{{__("Products list")}} -
|
||||
@endsection
|
||||
@section('filter')
|
||||
{{-- Other filters --}}
|
||||
<h2>
|
||||
<i class="ri-book-3-line"></i>
|
||||
{{__("Category")}}:
|
||||
</h2>
|
||||
<searchable-multi-select
|
||||
:items='{{\App\Models\Category::all(['id','name'])}}'
|
||||
title-field="name"
|
||||
value-field="id"
|
||||
xlang="{{config('app.locale')}}"
|
||||
xname="filter[category_id]"
|
||||
:xvalue='{{request()->input('filter.category_id','[]')}}'
|
||||
:close-on-Select="true"></searchable-multi-select>
|
||||
@endsection
|
||||
@section('bulk')
|
||||
{{-- <option value="-"> - </option> --}}
|
||||
|
||||
<option value="publish"> {{__("Publish")}} </option>
|
||||
<option value="draft"> {{__("Draft")}} </option>
|
||||
@endsection
|
@ -0,0 +1,107 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6 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-6 mt-3">
|
||||
<div class="form-group">
|
||||
<label for="name">
|
||||
{{__('Slug')}}
|
||||
</label>
|
||||
<input name="slug" type="text"
|
||||
id="slug"
|
||||
class="form-control @error('slug') is-invalid @enderror"
|
||||
placeholder="{{__('Slug')}}"
|
||||
value="{{old('slug',$item->slug??null)}}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-lg-3 mt-3">
|
||||
<div class="form-group">
|
||||
<label for="price">
|
||||
{{__('Base price')}}
|
||||
</label>
|
||||
|
||||
<currency-input name="price" xid="price" @error('price')
|
||||
:err="true" @enderror :xvalue="{{old('price',$item->price??null)}}"></currency-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 mt-3">
|
||||
<div class="form-group">
|
||||
<label for="categoryId">
|
||||
{{__('Main product category')}}1
|
||||
</label>
|
||||
|
||||
{{-- data-url="{{route('props.list','')}}/"--}}
|
||||
<searchable-select
|
||||
@error('category_id') :err="true" @enderror
|
||||
:items='@json($cats)'
|
||||
title-field="name"
|
||||
value-field="id"
|
||||
xlang="{{config('app.locale')}}"
|
||||
xid="categoryId"
|
||||
xname="category_id"
|
||||
@error('category_id') :err="true" @enderror
|
||||
xvalue='{{old('category_id',$item->category_id??null)}}'
|
||||
:close-on-Select="true"></searchable-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 mt-3">
|
||||
<div class="form-group">
|
||||
<label for="price">
|
||||
{{__('SKU')}}
|
||||
</label>
|
||||
<input name="sku" type="text"
|
||||
id="sku"
|
||||
class="form-control @error('sku') is-invalid @enderror"
|
||||
placeholder="{{__('SKU')}}"
|
||||
value="{{old('sku',$item->sku??null)}}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 mt-3">
|
||||
<div class="form-group">
|
||||
<label for="status">
|
||||
{{__('Status')}}
|
||||
</label>
|
||||
<select name="status" id="status"
|
||||
class="form-control @error('status') is-invalid @enderror">
|
||||
<option value="1"
|
||||
@if (old('status',$item->status??null) == '1' ) selected @endif >{{__("Published")}} </option>
|
||||
<option value="0"
|
||||
@if (old('status',$item->status??null) == '0' ) selected @endif >{{__("Draft")}} </option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 mt-3">
|
||||
<div class="form-group">
|
||||
<label for="excerpt">
|
||||
{{__('Excerpt')}}
|
||||
</label>
|
||||
<textarea name="excerpt"
|
||||
class="form-control @error('excerpt') is-invalid @enderror"
|
||||
placeholder="{{__('Excerpt')}}"
|
||||
id="excerpt"
|
||||
rows="4">{{old('excerpt',$item->excerpt??null)}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12 mt-3">
|
||||
<div class="form-group">
|
||||
<label for="description">
|
||||
{{__('Description Text')}}
|
||||
</label>
|
||||
<textarea name="desc" class="form-control ckeditorx @error('description') is-invalid @enderror"
|
||||
placeholder="{{__('Description Text')}}"
|
||||
id="description"
|
||||
rows="8">{{old('description',$item->description??null)}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,30 @@
|
||||
<div class="alert alert-info pt-4">
|
||||
<p>✓ {{__("The first and/or second image will be index image")}} <br>
|
||||
✓ {{__("You can choose one or more image together")}} <br>
|
||||
✓ {{__("Double click on image to change index image")}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="uploader-images">
|
||||
<input type="file" multiple accept=".jpg,.png,.gif" id="upload-image-select"/>
|
||||
</div>
|
||||
<div id="upload-drag-drop">
|
||||
<h2>
|
||||
{{__("Click here to upload or drag and drop here")}}
|
||||
</h2>
|
||||
</div>
|
||||
<div id="uploading-images" class="row">
|
||||
@if (isset($item))
|
||||
@foreach($item->getMedia() as $k => $media)
|
||||
<div data-id="-1" data-key="{{$k}}"
|
||||
class="image-index col-xl-3 col-md-4 border p-3 @if($k == $item->image_index) indexed @endif">
|
||||
<img class="img-list" src="{{$media->getUrl()}}" alt="{{$k}}">
|
||||
<div class="btn btn-danger upload-remove-image d-block">
|
||||
<span class="ri-close-line"></span>
|
||||
</div>
|
||||
<input type="hidden" name="medias[]" value="{{$media->id}}"/>
|
||||
</div>
|
||||
@endforeach
|
||||
<input type="hidden" name="index_image" id="index-image" value="{{$item?->image_index}}">
|
||||
@endif
|
||||
</div>
|
||||
|
@ -0,0 +1,177 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="stock_quantity" class="my-2">
|
||||
{{__('Stock quantity')}}
|
||||
</label>
|
||||
<input type="number" id="stock_quantity" name="stock_quantity"
|
||||
value="{{old('stock_quantity',$item->stock_quantity??0)}}"
|
||||
placeholder="{{__('Stock quantity')}}"
|
||||
class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="status" class="my-2">
|
||||
{{__("Status")}}
|
||||
</label>
|
||||
<select class="form-control" name="stock_status" id="status">
|
||||
@foreach(\App\Models\Product::$stock_status as $k => $v)
|
||||
<option
|
||||
value="{{ $k }}" {{ old("stock_status", $item->stock_status??null) == $k ? "selected" : "" }}>{{ __($v) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<label for="tags" class="mt-2">
|
||||
{{__("Tags")}}
|
||||
</label>
|
||||
<tag-input xname="tags" splitter=",," xid="tags"
|
||||
xtitle="{{__("Tags, Press enter")}}"
|
||||
@if(isset($item))
|
||||
xvalue="{{old('title',implode(',,',$item->tags->pluck('name')->toArray()??''))}}"
|
||||
@endif
|
||||
></tag-input>
|
||||
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h5>
|
||||
{{__("Categories")}}
|
||||
</h5>
|
||||
<ul class="group-control">
|
||||
{!!showCatNestedControl($cats,old('cat',isset($item)?$item->categories()->pluck('id')->toArray():[]))!!}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="table">
|
||||
{{__('Description Table')}}
|
||||
</label>
|
||||
<textarea name="table" class="ckeditorx @error('description') is-invalid @enderror"
|
||||
placeholder="{{__('Description Table')}}"
|
||||
id="table"
|
||||
rows="8">{{old('table',$item->table??null)}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion mt-2" id="accordionExample">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingTwo">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
||||
{{__("Discounts")}}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo"
|
||||
data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body">
|
||||
<table class="table" id="discounts">
|
||||
<tr>
|
||||
<th>
|
||||
{{__("Type")}}
|
||||
</th>
|
||||
<th>
|
||||
{{__("Amount")}}
|
||||
</th>
|
||||
<th>
|
||||
{{__("Discount code")}}
|
||||
</th>
|
||||
<th>
|
||||
{{__("Expire date")}}
|
||||
</th>
|
||||
<th>
|
||||
-
|
||||
</th>
|
||||
</tr>
|
||||
@if(isset($item))
|
||||
@foreach($item->discounts as $dis)
|
||||
<tr>
|
||||
<td>
|
||||
{{$dis->type}}
|
||||
</td>
|
||||
<td>
|
||||
{{$dis->amount}}
|
||||
</td>
|
||||
<td>
|
||||
{{$dis->code}}
|
||||
</td>
|
||||
<td>
|
||||
{{$dis->expire->jdate('Y/m/d')}}
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-danger" data-id="{{$dis->id}}">
|
||||
<span class="ri-close-line"></span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@endif
|
||||
</table>
|
||||
<input type="hidden" id="discount-rem" name="discount[remove]" value="[]">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingThree">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
|
||||
{{__("New Discount")}}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree"
|
||||
data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body">
|
||||
<table class="table" id="new-discount">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{{__("Type")}}
|
||||
</th>
|
||||
<th>
|
||||
{{__("Amount")}}
|
||||
</th>
|
||||
{{-- <th>--}}
|
||||
{{-- {{__("Discount code")}}--}}
|
||||
{{-- </th>--}}
|
||||
<th>
|
||||
{{__("Expire date")}}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label>
|
||||
{{__("by price")}}
|
||||
<input type="radio" checked name="discount[type]" value="price">
|
||||
</label>
|
||||
|
||||
|
||||
<label>
|
||||
{{__("by percent")}}
|
||||
<input type="radio" name="discount[type]" value="percent">
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="price-amount" placeholder="{{__("Amount")}}"
|
||||
name="discount[amount]" class="form-control">
|
||||
</td>
|
||||
{{-- <td>--}}
|
||||
{{-- <input type="text" placeholder="{{__("Discount code")}}" name="discount[code]" class="form-control">--}}
|
||||
{{-- </td>--}}
|
||||
<td>
|
||||
<input placeholder="{{__("Expire date")}}" type="text" data-reuslt="#exp-date"
|
||||
class="form-control dtp">
|
||||
<input type="hidden" name="discount[expire]" id="exp-date">
|
||||
</td>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,3 @@
|
||||
<h1>
|
||||
step4
|
||||
</h1>
|
Loading…
Reference in New Issue