first upload all files
This commit is contained in:
22
Modules/Option/Admin/OptionTable.php
Normal file
22
Modules/Option/Admin/OptionTable.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Admin;
|
||||
|
||||
use Modules\Admin\Ui\AdminTable;
|
||||
|
||||
class OptionTable extends AdminTable
|
||||
{
|
||||
/**
|
||||
* Make table response for the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function make()
|
||||
{
|
||||
return $this->newTable()
|
||||
->editColumn('type', function ($option) {
|
||||
return trans("option::options.form.option_types.{$option->type}");
|
||||
})
|
||||
->removeColumn('values');
|
||||
}
|
||||
}
|
||||
36
Modules/Option/Admin/OptionTabs.php
Normal file
36
Modules/Option/Admin/OptionTabs.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Admin;
|
||||
|
||||
use Modules\Admin\Ui\Tab;
|
||||
use Modules\Admin\Ui\Tabs;
|
||||
|
||||
class OptionTabs extends Tabs
|
||||
{
|
||||
public function make()
|
||||
{
|
||||
$this->group('option_information', trans('option::options.tabs.group.option_information'))
|
||||
->active()
|
||||
->add($this->general())
|
||||
->add($this->values());
|
||||
}
|
||||
|
||||
private function general()
|
||||
{
|
||||
return tap(new Tab('general', trans('option::options.tabs.general')), function (Tab $tab) {
|
||||
$tab->active();
|
||||
$tab->weight(10);
|
||||
$tab->fields(['name', 'type', 'is_required']);
|
||||
$tab->view('option::admin.options.tabs.general');
|
||||
});
|
||||
}
|
||||
|
||||
private function values()
|
||||
{
|
||||
return tap(new Tab('values', trans('option::options.tabs.values')), function (Tab $tab) {
|
||||
$tab->weight(20);
|
||||
$tab->fields(['values.*.label', 'values.*.price', 'values.*.price_type']);
|
||||
$tab->view('option::admin.options.tabs.values');
|
||||
});
|
||||
}
|
||||
}
|
||||
39
Modules/Option/Admin/ProductTabsExtender.php
Normal file
39
Modules/Option/Admin/ProductTabsExtender.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Admin;
|
||||
|
||||
use Modules\Admin\Ui\Tab;
|
||||
use Modules\Admin\Ui\Tabs;
|
||||
use Modules\Option\Entities\Option;
|
||||
|
||||
class ProductTabsExtender
|
||||
{
|
||||
public function extend(Tabs $tabs)
|
||||
{
|
||||
$tabs->group('advanced_information')
|
||||
->add($this->options());
|
||||
}
|
||||
|
||||
private function options()
|
||||
{
|
||||
if (! auth()->user()->hasAccess(['admin.options.create', 'admin.options.edit'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
return tap(new Tab('options', trans('option::options.tabs.product.options')), function (Tab $tab) {
|
||||
$tab->weight(35);
|
||||
|
||||
$tab->fields([
|
||||
'options.*.name',
|
||||
'options.*.type',
|
||||
'options.*.values.*.label',
|
||||
'options.*.values.*.price',
|
||||
'options.*.values.*.price_type',
|
||||
]);
|
||||
|
||||
$tab->view('option::admin.products.tabs.options', [
|
||||
'globalOptions' => Option::globals()->get(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
22
Modules/Option/Config/assets.php
Normal file
22
Modules/Option/Config/assets.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Define which assets will be available through the asset manager
|
||||
|--------------------------------------------------------------------------
|
||||
| These assets are registered on the asset manager
|
||||
*/
|
||||
'all_assets' => [
|
||||
'admin.option.css' => ['module' => 'option:admin/css/option.css'],
|
||||
'admin.option.js' => ['module' => 'option:admin/js/option.js'],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Define which default assets will always be included in your pages
|
||||
| through the asset pipeline
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'required_assets' => [],
|
||||
];
|
||||
10
Modules/Option/Config/permissions.php
Normal file
10
Modules/Option/Config/permissions.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'admin.options' => [
|
||||
'index' => 'option::permissions.options.index',
|
||||
'create' => 'option::permissions.options.create',
|
||||
'edit' => 'option::permissions.options.edit',
|
||||
'destroy' => 'option::permissions.options.destroy',
|
||||
],
|
||||
];
|
||||
24
Modules/Option/Database/Factories/ModelFactory.php
Normal file
24
Modules/Option/Database/Factories/ModelFactory.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Faker\Generator as Faker;
|
||||
use Modules\Option\Entities\Option;
|
||||
use Modules\Option\Entities\OptionValue;
|
||||
|
||||
$factory->define(OptionValue::class, function (Faker $faker) {
|
||||
return [
|
||||
'option_id' => function () {
|
||||
return factory(Option::class)->create()->id;
|
||||
},
|
||||
'name' => $faker->word(),
|
||||
'price' => $faker->randomNumber(),
|
||||
'price_type' => $faker->randomElement(['fixed', 'percent']),
|
||||
];
|
||||
});
|
||||
|
||||
$factory->define(Option::class, function (Faker $faker) {
|
||||
return [
|
||||
'name' => $faker->word(),
|
||||
'type' => $faker->randomElement(['field', 'dropdown']),
|
||||
'is_required' => $faker->boolean(),
|
||||
];
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateOptionsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('options', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('type');
|
||||
$table->boolean('is_required');
|
||||
$table->boolean('is_global')->default(true);
|
||||
$table->integer('position')->unsigned()->nullable();
|
||||
$table->softDeletes();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('options');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateOptionTranslationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('option_translations', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('option_id')->unsigned();
|
||||
$table->string('locale');
|
||||
$table->string('name');
|
||||
|
||||
$table->unique(['option_id', 'locale']);
|
||||
$table->foreign('option_id')->references('id')->on('options')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('option_translations');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateOptionValuesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('option_values', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('option_id')->unsigned()->index();
|
||||
$table->decimal('price', 18, 4)->unsigned()->nullable();
|
||||
$table->string('price_type', 10);
|
||||
$table->integer('position')->unsigned();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('option_id')->references('id')->on('options')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('option_values');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateOptionValueTranslationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('option_value_translations', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('option_value_id')->unsigned();
|
||||
$table->string('locale');
|
||||
$table->string('label');
|
||||
|
||||
$table->unique(['option_value_id', 'locale']);
|
||||
$table->foreign('option_value_id')->references('id')->on('option_values')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('option_value_translations');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateProductOptionsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('product_options', function (Blueprint $table) {
|
||||
$table->integer('product_id')->unsigned();
|
||||
$table->integer('option_id')->unsigned();
|
||||
|
||||
$table->primary(['product_id', 'option_id']);
|
||||
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
|
||||
$table->foreign('option_id')->references('id')->on('options')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('product_options');
|
||||
}
|
||||
}
|
||||
26
Modules/Option/Database/Seeders/OptionDatabaseSeeder.php
Normal file
26
Modules/Option/Database/Seeders/OptionDatabaseSeeder.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Modules\Option\Entities\Option;
|
||||
use Modules\Option\Entities\OptionValue;
|
||||
|
||||
class OptionDatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
factory(Option::class, 10)
|
||||
->create()
|
||||
->each(function ($option) {
|
||||
$times = $option->type === 'dropdown' ? 5 : 1;
|
||||
|
||||
factory(OptionValue::class, $times)->create(['option_id' => $option->id]);
|
||||
});
|
||||
}
|
||||
}
|
||||
141
Modules/Option/Entities/Option.php
Normal file
141
Modules/Option/Entities/Option.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Entities;
|
||||
|
||||
use Modules\Support\Eloquent\Model;
|
||||
use Modules\Option\Admin\OptionTable;
|
||||
use Modules\Support\Eloquent\Translatable;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Option extends Model
|
||||
{
|
||||
use Translatable, SoftDeletes;
|
||||
|
||||
/**
|
||||
* Available option types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const TYPES = [
|
||||
'field', 'textarea', 'dropdown', 'checkbox', 'checkbox_custom',
|
||||
'radio', 'radio_custom', 'multiple_select', 'date', 'date_time', 'time',
|
||||
];
|
||||
|
||||
/**
|
||||
* The relations to eager load on every query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $with = ['translations', 'values'];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['option', 'type', 'is_required', 'is_global', 'position'];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'is_required' => 'boolean',
|
||||
'is_global' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
/**
|
||||
* The attributes that are translatable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $translatedAttributes = ['name'];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::saved(function ($option) {
|
||||
if (request()->routeIs('admin.options.*')) {
|
||||
$option->saveValues(request('values', []));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function isFieldType()
|
||||
{
|
||||
return in_array($this->type, ['field', 'textarea', 'dropdown', 'radio', 'date', 'date_time', 'time']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the values for the option.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function values()
|
||||
{
|
||||
return $this->hasMany(OptionValue::class)->orderBy('position');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include global options.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeGlobals($query)
|
||||
{
|
||||
return $query->where('is_global', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table data for the resource
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function table()
|
||||
{
|
||||
return new OptionTable($this->newQuery()->globals());
|
||||
}
|
||||
|
||||
/**
|
||||
* Save values for the option.
|
||||
*
|
||||
* @param array $values
|
||||
* @return void
|
||||
*/
|
||||
public function saveValues($values = [])
|
||||
{
|
||||
$ids = $this->getDeleteCandidates($values);
|
||||
|
||||
if ($ids->isNotEmpty()) {
|
||||
$this->values()->whereIn('id', $ids)->delete();
|
||||
}
|
||||
|
||||
foreach (array_reset_index($values) as $index => $attributes) {
|
||||
$attributes += ['position' => $index];
|
||||
|
||||
$this->values()->updateOrCreate([
|
||||
'id' => array_get($attributes, 'id'),
|
||||
], $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
private function getDeleteCandidates($values)
|
||||
{
|
||||
return $this->values()
|
||||
->pluck('id')
|
||||
->diff(array_pluck($values, 'id'));
|
||||
}
|
||||
}
|
||||
15
Modules/Option/Entities/OptionTranslation.php
Normal file
15
Modules/Option/Entities/OptionTranslation.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Entities;
|
||||
|
||||
use Modules\Support\Eloquent\TranslationModel;
|
||||
|
||||
class OptionTranslation extends TranslationModel
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['name'];
|
||||
}
|
||||
84
Modules/Option/Entities/OptionValue.php
Normal file
84
Modules/Option/Entities/OptionValue.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Entities;
|
||||
|
||||
use Modules\Support\Money;
|
||||
use Modules\Support\Eloquent\Model;
|
||||
use Modules\Product\Entities\Product;
|
||||
use Modules\Support\Eloquent\Translatable;
|
||||
|
||||
class OptionValue extends Model
|
||||
{
|
||||
use Translatable;
|
||||
|
||||
/**
|
||||
* The relations to eager load on every query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $with = ['translations'];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['price', 'price_type', 'position'];
|
||||
|
||||
/**
|
||||
* The attributes that are translatable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $translatedAttributes = ['label'];
|
||||
|
||||
public function getPriceAttribute($price)
|
||||
{
|
||||
if ($this->priceIsPercent()) {
|
||||
return $price;
|
||||
}
|
||||
|
||||
return Money::inDefaultCurrency($price);
|
||||
}
|
||||
|
||||
public function priceIsPercent()
|
||||
{
|
||||
return $this->price_type === 'percent';
|
||||
}
|
||||
|
||||
public function priceIsFixed()
|
||||
{
|
||||
return $this->price_type === 'fixed';
|
||||
}
|
||||
|
||||
public function priceForProduct(Product $product)
|
||||
{
|
||||
if ($this->priceIsFixed()) {
|
||||
return $this->price;
|
||||
}
|
||||
|
||||
return $this->getPercentOf($product->selling_price->amount());
|
||||
}
|
||||
|
||||
private function getPercentOf($productPrice)
|
||||
{
|
||||
return Money::inDefaultCurrency(($this->price / 100) * $productPrice);
|
||||
}
|
||||
|
||||
public function formattedPriceForProduct(Product $product, $forSelectOption = false)
|
||||
{
|
||||
$price = $this->priceForProduct($product);
|
||||
|
||||
if ($price->isZero()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$formattedPrice = $price->convertToCurrentCurrency()->format();
|
||||
|
||||
if ($forSelectOption) {
|
||||
return "+ {$formattedPrice}";
|
||||
}
|
||||
|
||||
return "<span class='extra-price'>+ {$formattedPrice}</span>";
|
||||
}
|
||||
}
|
||||
15
Modules/Option/Entities/OptionValueTranslation.php
Normal file
15
Modules/Option/Entities/OptionValueTranslation.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Entities;
|
||||
|
||||
use Modules\Support\Eloquent\TranslationModel;
|
||||
|
||||
class OptionValueTranslation extends TranslationModel
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['label'];
|
||||
}
|
||||
40
Modules/Option/Http/Controllers/Admin/OptionController.php
Normal file
40
Modules/Option/Http/Controllers/Admin/OptionController.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Http\Controllers\Admin;
|
||||
|
||||
use Modules\Option\Entities\Option;
|
||||
use Modules\Admin\Traits\HasCrudActions;
|
||||
use Modules\Option\Http\Requests\SaveOptionRequest;
|
||||
|
||||
class OptionController
|
||||
{
|
||||
use HasCrudActions;
|
||||
|
||||
/**
|
||||
* Model for the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = Option::class;
|
||||
|
||||
/**
|
||||
* Label of the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label = 'option::options.option';
|
||||
|
||||
/**
|
||||
* View path of the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $viewPath = 'option::admin.options';
|
||||
|
||||
/**
|
||||
* Form requests for the resource.
|
||||
*
|
||||
* @var array|string
|
||||
*/
|
||||
protected $validation = SaveOptionRequest::class;
|
||||
}
|
||||
52
Modules/Option/Http/Requests/SaveOptionRequest.php
Normal file
52
Modules/Option/Http/Requests/SaveOptionRequest.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Modules\Option\Entities\Option;
|
||||
use Modules\Core\Http\Requests\Request;
|
||||
|
||||
class SaveOptionRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Available attributes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $availableAttributes = 'option::attributes';
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'name' => 'required',
|
||||
'type' => ['required', Rule::in(Option::TYPES)],
|
||||
'is_required' => 'required|boolean',
|
||||
'values.*.label' => 'required_if:options.*.type,dropdown,checkbox,checkbox_custom,radio,radio_custom,multiple_select',
|
||||
'values.*.price' => 'nullable|numeric|min:0|max:99999999999999',
|
||||
'values.*.price_type' => ['required', Rule::in(['fixed', 'percent'])],
|
||||
];
|
||||
}
|
||||
|
||||
public function validationData()
|
||||
{
|
||||
return request()->merge([
|
||||
'values' => $this->filter($this->values ?? []),
|
||||
])->all();
|
||||
}
|
||||
|
||||
private function filter($values = [])
|
||||
{
|
||||
return array_filter($values, function ($value) {
|
||||
if (! array_has($value, 'label')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! is_null($value['label']);
|
||||
});
|
||||
}
|
||||
}
|
||||
34
Modules/Option/Http/Requests/SaveProductOptionsRequest.php
Normal file
34
Modules/Option/Http/Requests/SaveProductOptionsRequest.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Modules\Option\Entities\Option;
|
||||
use Modules\Core\Http\Requests\Request;
|
||||
|
||||
class SaveProductOptionsRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Available attributes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $availableAttributes = 'option::attributes';
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'options.*.name' => 'required_with:options.*.type',
|
||||
'options.*.type' => ['nullable', 'required_with:options.*.name', Rule::in(Option::TYPES)],
|
||||
'options.*.is_required' => ['required_with:options.*.name', 'boolean'],
|
||||
'options.*.values.*.label' => 'required_if:options.*.type,dropdown,checkbox,checkbox_custom,radio,radio_custom,multiple_select',
|
||||
'options.*.values.*.price' => 'nullable|numeric|min:0|max:99999999999999',
|
||||
'options.*.values.*.price_type' => ['required', Rule::in(['fixed', 'percent'])],
|
||||
];
|
||||
}
|
||||
}
|
||||
48
Modules/Option/Listeners/SaveProductOptions.php
Normal file
48
Modules/Option/Listeners/SaveProductOptions.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Listeners;
|
||||
|
||||
class SaveProductOptions
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param \Modules\Product\Entities\Product $product
|
||||
* @return void
|
||||
*/
|
||||
public function handle($product)
|
||||
{
|
||||
$ids = $this->getDeleteCandidates($product);
|
||||
|
||||
if ($ids->isNotEmpty()) {
|
||||
$product->options()->detach($ids);
|
||||
}
|
||||
|
||||
$this->saveOptions($product);
|
||||
}
|
||||
|
||||
private function getDeleteCandidates($product)
|
||||
{
|
||||
return $product->options()
|
||||
->pluck('id')
|
||||
->diff(array_pluck($this->options(), 'id'));
|
||||
}
|
||||
|
||||
private function saveOptions($product)
|
||||
{
|
||||
foreach (array_reset_index($this->options()) as $index => $attributes) {
|
||||
$attributes += ['is_global' => false, 'position' => $index];
|
||||
|
||||
$option = $product->options()->updateOrCreate(['id' => $attributes['id'] ?? null], $attributes);
|
||||
|
||||
$option->saveValues($attributes['values'] ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
private function options()
|
||||
{
|
||||
return array_filter(request('options', []), function ($option) {
|
||||
return ! is_null($option['name']);
|
||||
});
|
||||
}
|
||||
}
|
||||
27
Modules/Option/Providers/EventServiceProvider.php
Normal file
27
Modules/Option/Providers/EventServiceProvider.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Providers;
|
||||
|
||||
use Modules\Product\Entities\Product;
|
||||
use Modules\Option\Listeners\SaveProductOptions;
|
||||
use Modules\Option\Http\Requests\SaveProductOptionsRequest;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap the application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
Product::saving(function () {
|
||||
resolve(SaveProductOptionsRequest::class);
|
||||
});
|
||||
|
||||
Product::saved(SaveProductOptions::class);
|
||||
}
|
||||
}
|
||||
27
Modules/Option/Providers/OptionServiceProvider.php
Normal file
27
Modules/Option/Providers/OptionServiceProvider.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Providers;
|
||||
|
||||
use Modules\Option\Admin\OptionTabs;
|
||||
use Modules\Support\Traits\AddsAsset;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Admin\Ui\Facades\TabManager;
|
||||
use Modules\Option\Admin\ProductTabsExtender;
|
||||
|
||||
class OptionServiceProvider extends ServiceProvider
|
||||
{
|
||||
use AddsAsset;
|
||||
|
||||
/**
|
||||
* Bootstrap the application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
TabManager::register('options', OptionTabs::class);
|
||||
TabManager::extend('products', ProductTabsExtender::class);
|
||||
|
||||
$this->addAdminAssets('admin.(options|products).(create|edit)', ['admin.option.css', 'admin.option.js']);
|
||||
}
|
||||
}
|
||||
114
Modules/Option/Resources/assets/admin/js/BaseOption.js
Normal file
114
Modules/Option/Resources/assets/admin/js/BaseOption.js
Normal file
@@ -0,0 +1,114 @@
|
||||
export default class {
|
||||
addOptionsErrors(errors) {
|
||||
for (let key in errors) {
|
||||
let inputField = this.getInputFieldForErrorKey(key);
|
||||
|
||||
inputField.closest('.option').addClass('option-has-errors');
|
||||
|
||||
let parent = inputField.parent();
|
||||
|
||||
parent.append(`<span class="help-block">${errors[key][0]}</span>`);
|
||||
}
|
||||
}
|
||||
|
||||
getRowTemplate(data) {
|
||||
let template = _.template($('#option-select-values-template').html());
|
||||
|
||||
return $(template(data));
|
||||
}
|
||||
|
||||
changeOptionType({ optionId, type, values = [] }) {
|
||||
let optionValuesElement = this.getOptionValuesElement(optionId);
|
||||
let templateType = this.getTemplateType(type, optionValuesElement);
|
||||
let optionValuesData = { optionId, value: { id: '', label: '', price: '', price_type: 'fixed' } };
|
||||
|
||||
if (this.shouldNotChangeTemplate(templateType, optionValuesElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (values.length !== 0 && templateType === 'text') {
|
||||
optionValuesData.value = values[0];
|
||||
}
|
||||
|
||||
let template = _.template($(`#option-${templateType}-template`).html());
|
||||
|
||||
optionValuesElement.html(template(optionValuesData));
|
||||
|
||||
if (templateType === 'select') {
|
||||
this.addOptionRowEventListener(optionId);
|
||||
|
||||
this.addOptionRows({ optionId, values });
|
||||
|
||||
if (values.length === 0) {
|
||||
this.getAddNewRowButton(optionId).trigger('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addOptionRows({ optionId, values }) {
|
||||
for (let [index, value] of values.entries()) {
|
||||
this.addOptionRow({
|
||||
optionId,
|
||||
valueId: index,
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getTemplateType(type) {
|
||||
if (this.templateTypeIsText(type)) {
|
||||
return 'text';
|
||||
}
|
||||
|
||||
if (this.templateTypeIsSelect(type)) {
|
||||
return 'select';
|
||||
}
|
||||
}
|
||||
|
||||
templateTypeIsText(type) {
|
||||
return ['field', 'textarea', 'date', 'date_time', 'time'].includes(type);
|
||||
}
|
||||
|
||||
templateTypeIsSelect(type) {
|
||||
return ['dropdown', 'checkbox', 'checkbox_custom', 'radio', 'radio_custom', 'multiple_select'].includes(type);
|
||||
}
|
||||
|
||||
shouldNotChangeTemplate(templateType, optionValuesElement) {
|
||||
return templateType === undefined || this.alreadyHasCurrentTemplate(templateType, optionValuesElement);
|
||||
}
|
||||
|
||||
alreadyHasCurrentTemplate(templateType, optionValuesElement) {
|
||||
if (templateType === 'text') {
|
||||
return optionValuesElement.children().hasClass('option-text');
|
||||
}
|
||||
|
||||
if (templateType === 'select') {
|
||||
return optionValuesElement.children().hasClass('option-select');
|
||||
}
|
||||
}
|
||||
|
||||
initOptionRow(template, selectValues) {
|
||||
if (selectValues.length !== 0 && ! selectValues.is('.sortable')) {
|
||||
this.makeSortable(selectValues[0]);
|
||||
|
||||
selectValues.addClass('sortable');
|
||||
}
|
||||
|
||||
this.deleteOptionRowEventListener(template);
|
||||
|
||||
window.admin.tooltip();
|
||||
}
|
||||
|
||||
deleteOptionRowEventListener(row) {
|
||||
row.find('.delete-row').on('click', (e) => {
|
||||
$(e.currentTarget).closest('.option-row').remove();
|
||||
});
|
||||
}
|
||||
|
||||
makeSortable(el) {
|
||||
Sortable.create(el, {
|
||||
handle: '.drag-icon',
|
||||
animation: 150,
|
||||
});
|
||||
}
|
||||
}
|
||||
53
Modules/Option/Resources/assets/admin/js/Option.js
Normal file
53
Modules/Option/Resources/assets/admin/js/Option.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import BaseOption from './BaseOption';
|
||||
|
||||
export default class extends BaseOption {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
let values = FleetCart.data['option.values'];
|
||||
|
||||
$('#type').on('change', (e) => {
|
||||
super.changeOptionType({ type: e.currentTarget.value, values });
|
||||
super.addOptionsErrors(FleetCart.errors['option.values']);
|
||||
});
|
||||
|
||||
$('#type').trigger('change');
|
||||
|
||||
window.admin.removeSubmitButtonOffsetOn('#values');
|
||||
}
|
||||
|
||||
addOptionRow({ valueId, value = { label: '', price: '', price_type: 'fixed' } }) {
|
||||
let template = this.getRowTemplate({ optionId: undefined, valueId, value });
|
||||
|
||||
let selectValues = $('#select-values').append(template);
|
||||
|
||||
super.initOptionRow(template, selectValues);
|
||||
}
|
||||
|
||||
addOptionRowEventListener() {
|
||||
$('#add-new-row').on('click', () => {
|
||||
let valueId = $('#option-values .option-row').length;
|
||||
|
||||
this.addOptionRow({ valueId });
|
||||
});
|
||||
}
|
||||
|
||||
getOptionValuesElement() {
|
||||
return $('#option-values');
|
||||
}
|
||||
|
||||
getAddNewRowButton() {
|
||||
return $('#add-new-row');
|
||||
}
|
||||
|
||||
getInputFieldForErrorKey(key) {
|
||||
let keyParts = key.split('.');
|
||||
|
||||
// Replace all "_" to "-".
|
||||
keyParts = keyParts.map(k => {
|
||||
return k.split('_').join('-');
|
||||
});
|
||||
|
||||
return $(`#${keyParts.join('-')}`);
|
||||
}
|
||||
}
|
||||
161
Modules/Option/Resources/assets/admin/js/ProductOption.js
Normal file
161
Modules/Option/Resources/assets/admin/js/ProductOption.js
Normal file
@@ -0,0 +1,161 @@
|
||||
import BaseOption from './BaseOption';
|
||||
|
||||
export default class extends BaseOption {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.optionsCount = 0;
|
||||
|
||||
this.addOptions(FleetCart.data['product.options']);
|
||||
|
||||
if (this.optionsCount === 0) {
|
||||
this.addOption();
|
||||
}
|
||||
|
||||
if (this.optionsCount > 3) {
|
||||
this.collapseOptions();
|
||||
}
|
||||
|
||||
super.addOptionsErrors(FleetCart.errors['product.options']);
|
||||
|
||||
$('#add-new-option').on('click', () => this.addOption());
|
||||
$('#add-global-option').on('click', () => this.addGlobalOption());
|
||||
}
|
||||
|
||||
addOptions(options) {
|
||||
for (let option of options) {
|
||||
this.addOption(option);
|
||||
}
|
||||
}
|
||||
|
||||
collapseOptions() {
|
||||
let options = $('.option:not(.option-has-errors)');
|
||||
|
||||
for (let option of options) {
|
||||
$(option).find('[data-toggle=collapse]').trigger('click');
|
||||
}
|
||||
}
|
||||
|
||||
addGlobalOption() {
|
||||
let globalOptionId = $('#global-option').val();
|
||||
|
||||
if (globalOptionId === '') {
|
||||
return window.admin.stopButtonLoading($('#add-global-option'));
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: route('admin.options.show', globalOptionId),
|
||||
dataType: 'json',
|
||||
success: option => {
|
||||
this.addOption(option);
|
||||
|
||||
window.admin.stopButtonLoading($('#add-global-option'));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
addOption(option = { id: null, name: null, type: null, is_required: false, values: [] }) {
|
||||
// Cast "is_required" field to boolean.
|
||||
option.is_required = !! JSON.parse(option.is_required);
|
||||
|
||||
let optionId = this.optionsCount;
|
||||
|
||||
let template = _.template($('#option-template').html());
|
||||
let html = $(template({ option, optionId }));
|
||||
|
||||
if (option.name !== null) {
|
||||
setTimeout(() => {
|
||||
$(`#option-${optionId}`).find('#option-name').text(option.name);
|
||||
});
|
||||
}
|
||||
|
||||
let optionGroup = $('#options-group').append(html);
|
||||
|
||||
if (! optionGroup.is('.sortable')) {
|
||||
super.makeSortable(optionGroup[0]);
|
||||
|
||||
optionGroup.addClass('sortable');
|
||||
}
|
||||
|
||||
this.deleteOptionEventListener(html);
|
||||
this.updateOptionNameEventListener(optionId);
|
||||
this.updateTemplateEventListener(optionId, option['values']);
|
||||
|
||||
window.admin.tooltip();
|
||||
|
||||
this.optionsCount++;
|
||||
}
|
||||
|
||||
deleteOptionEventListener(option) {
|
||||
option.find('.delete-option').on('click', (e) => $(e.currentTarget).closest('.option').remove());
|
||||
}
|
||||
|
||||
updateOptionNameEventListener(optionId) {
|
||||
let option = $(`#option-${optionId}`);
|
||||
let old = option.find('#option-name').text();
|
||||
|
||||
$(option).find('.option-name-field').on('input', (e) => {
|
||||
let name = e.currentTarget.value !== '' ? e.currentTarget.value : old;
|
||||
|
||||
option.find('#option-name').text(name);
|
||||
});
|
||||
}
|
||||
|
||||
updateTemplateEventListener(optionId, values = []) {
|
||||
let optionTypeElement = $(`#option-${optionId}-type`);
|
||||
|
||||
optionTypeElement.on('change', (e) => {
|
||||
let type = e.currentTarget.value;
|
||||
|
||||
if (type === '') {
|
||||
return this.getOptionValuesElement(optionId).html('');
|
||||
}
|
||||
|
||||
super.changeOptionType({ optionId, type, values });
|
||||
});
|
||||
|
||||
// Trigger the "change" event on option type after attaching the listener
|
||||
// this will automatically take effect of the current option which is
|
||||
// maybe loaded from the old input values or from the product data.
|
||||
optionTypeElement.trigger('change');
|
||||
}
|
||||
|
||||
addOptionRow({ optionId, valueId, value = { label: '', price: '', price_type: 'fixed' } }) {
|
||||
let template = this.getRowTemplate({ optionId, valueId, value });
|
||||
|
||||
let selectValues = $(`#option-${optionId}-select-values`).append(template);
|
||||
|
||||
super.initOptionRow(template, selectValues);
|
||||
}
|
||||
|
||||
addOptionRowEventListener(optionId) {
|
||||
$(`#option-${optionId}-add-new-row`).on('click', () => {
|
||||
let valueId = $(`#option-${optionId}-values .option-row`).length;
|
||||
|
||||
this.addOptionRow({ optionId, valueId });
|
||||
});
|
||||
}
|
||||
|
||||
getOptionValuesElement(optionId) {
|
||||
return $(`#option-${optionId}-values`);
|
||||
}
|
||||
|
||||
getAddNewRowButton(optionId) {
|
||||
return $(`#option-${optionId}-add-new-row`);
|
||||
}
|
||||
|
||||
getInputFieldForErrorKey(key) {
|
||||
let keyParts = key.split('.');
|
||||
|
||||
// Remove the first element which is "option".
|
||||
keyParts.shift();
|
||||
|
||||
// Replace all "_" to "-".
|
||||
keyParts = keyParts.map(k => {
|
||||
return k.split('_').join('-');
|
||||
});
|
||||
|
||||
return $(`#option-${keyParts.join('-')}`);
|
||||
}
|
||||
}
|
||||
10
Modules/Option/Resources/assets/admin/js/main.js
Normal file
10
Modules/Option/Resources/assets/admin/js/main.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import Option from './Option';
|
||||
import ProductOption from './ProductOption';
|
||||
|
||||
if ($('#option-create-form, #option-edit-form').length !== 0) {
|
||||
new Option();
|
||||
}
|
||||
|
||||
if ($('#product-create-form, #product-edit-form').length !== 0) {
|
||||
new ProductOption();
|
||||
}
|
||||
127
Modules/Option/Resources/assets/admin/sass/main.scss
Normal file
127
Modules/Option/Resources/assets/admin/sass/main.scss
Normal file
@@ -0,0 +1,127 @@
|
||||
.new-option {
|
||||
.checkbox {
|
||||
margin: 30px 0 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.delete-option {
|
||||
margin-top: 25px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.options-group-wrapper {
|
||||
.panel-title a {
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
|
||||
> .drag-icon {
|
||||
font-size: 16px;
|
||||
color: #737881;
|
||||
cursor: move;
|
||||
vertical-align: top;
|
||||
margin: 4px 10px 0 0;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
i {
|
||||
float: left;
|
||||
|
||||
&:nth-child(2) {
|
||||
margin-left: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
.form-group {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
thead th {
|
||||
&:first-child {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
width: 60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.option-select {
|
||||
.options {
|
||||
.option-row {
|
||||
td {
|
||||
&:nth-child(2) {
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
min-width: 110px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-global-option {
|
||||
.form-group {
|
||||
margin-left: 0 !important;
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
|
||||
> button {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 1199px) {
|
||||
.add-global-option {
|
||||
float: none !important;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.new-option {
|
||||
.checkbox {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.delete-option {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.p-l-0 {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.options-group-wrapper {
|
||||
.option-values {
|
||||
.option-text,
|
||||
.option-select {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 991px) {
|
||||
.new-option {
|
||||
.checkbox {
|
||||
padding-top: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Modules/Option/Resources/lang/en/attributes.php
Normal file
21
Modules/Option/Resources/lang/en/attributes.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'name' => 'Name',
|
||||
'type' => 'Type',
|
||||
'is_required' => 'Required',
|
||||
'label' => 'Label',
|
||||
'price' => 'Price',
|
||||
'price_type' => 'Price Type',
|
||||
|
||||
// Validations
|
||||
'values.*.label' => 'Label',
|
||||
'values.*.price' => 'Price',
|
||||
'values.*.price_type' => 'Price Type',
|
||||
|
||||
'options.*.name' => 'Name',
|
||||
'options.*.type' => 'Type',
|
||||
'options.*.values.*.label' => 'Label',
|
||||
'options.*.values.*.price' => 'Price',
|
||||
'options.*.values.*.price_type' => 'Price Type',
|
||||
];
|
||||
52
Modules/Option/Resources/lang/en/options.php
Normal file
52
Modules/Option/Resources/lang/en/options.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'option' => 'Option',
|
||||
'options' => 'Options',
|
||||
'select_global_option' => 'Select Global Option',
|
||||
'please_select_a_option_type' => 'Please select a option type.',
|
||||
'table' => [
|
||||
'name' => 'Name',
|
||||
'type' => 'Type',
|
||||
],
|
||||
'tabs' => [
|
||||
'group' => [
|
||||
'option_information' => 'Option Information',
|
||||
],
|
||||
'general' => 'General',
|
||||
'values' => 'Values',
|
||||
'product' => [
|
||||
'options' => 'Options',
|
||||
],
|
||||
],
|
||||
'form' => [
|
||||
'this_option_is_required' => 'This option is required',
|
||||
'add_new_option' => 'Add New Option',
|
||||
'add_global_option' => 'Add Global Option',
|
||||
'new_option' => 'New Option',
|
||||
'option_types' => [
|
||||
'please_select' => 'Please Select',
|
||||
'text' => 'Text',
|
||||
'field' => 'Field',
|
||||
'textarea' => 'Textarea',
|
||||
'select' => 'Select',
|
||||
'dropdown' => 'Dropdown',
|
||||
'checkbox' => 'Checkbox',
|
||||
'checkbox_custom' => 'Custom Checkbox',
|
||||
'radio' => 'Radio Button',
|
||||
'radio_custom' => 'Custom Radio Button',
|
||||
'multiple_select' => 'Multiple Select',
|
||||
'date' => 'Date',
|
||||
'date_time' => 'Date & Time',
|
||||
'time' => 'Time',
|
||||
],
|
||||
'delete_option' => 'Delete Option',
|
||||
'price' => 'Price',
|
||||
'price_types' => [
|
||||
'fixed' => 'Fixed',
|
||||
'percent' => 'Percent',
|
||||
],
|
||||
'add_new_row' => 'Add New Row',
|
||||
'delete_row' => 'Delete Row',
|
||||
],
|
||||
];
|
||||
10
Modules/Option/Resources/lang/en/permissions.php
Normal file
10
Modules/Option/Resources/lang/en/permissions.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'options' => [
|
||||
'index' => 'Index Options',
|
||||
'create' => 'Create Options',
|
||||
'edit' => 'Edit Options',
|
||||
'destroy' => 'Delete Options',
|
||||
],
|
||||
];
|
||||
5
Modules/Option/Resources/lang/en/sidebar.php
Normal file
5
Modules/Option/Resources/lang/en/sidebar.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'options' => 'Options',
|
||||
];
|
||||
@@ -0,0 +1,18 @@
|
||||
@extends('admin::layout')
|
||||
|
||||
@component('admin::components.page.header')
|
||||
@slot('title', trans('admin::resource.create', ['resource' => trans('option::options.option')]))
|
||||
|
||||
<li><a href="{{ route('admin.options.index') }}">{{ trans('option::options.options') }}</a></li>
|
||||
<li class="active">{{ trans('admin::resource.create', ['resource' => trans('option::options.option')]) }}</li>
|
||||
@endcomponent
|
||||
|
||||
@section('content')
|
||||
<form method="POST" action="{{ route('admin.options.store') }}" class="form-horizontal" id="option-create-form" novalidate>
|
||||
{{ csrf_field() }}
|
||||
|
||||
{!! $tabs->render(compact('option')) !!}
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@include('option::admin.options.partials.scripts')
|
||||
20
Modules/Option/Resources/views/admin/options/edit.blade.php
Normal file
20
Modules/Option/Resources/views/admin/options/edit.blade.php
Normal file
@@ -0,0 +1,20 @@
|
||||
@extends('admin::layout')
|
||||
|
||||
@component('admin::components.page.header')
|
||||
@slot('title', trans('admin::resource.edit', ['resource' => trans('option::options.option')]))
|
||||
@slot('subtitle', $option->name)
|
||||
|
||||
<li><a href="{{ route('admin.options.index') }}">{{ trans('option::options.options') }}</a></li>
|
||||
<li class="active">{{ trans('admin::resource.edit', ['resource' => trans('option::options.option')]) }}</li>
|
||||
@endcomponent
|
||||
|
||||
@section('content')
|
||||
<form method="POST" action="{{ route('admin.options.update', $option) }}" class="form-horizontal" id="option-edit-form" novalidate>
|
||||
{{ csrf_field() }}
|
||||
{{ method_field('put') }}
|
||||
|
||||
{!! $tabs->render(compact('option')) !!}
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@include('option::admin.options.partials.scripts')
|
||||
38
Modules/Option/Resources/views/admin/options/index.blade.php
Normal file
38
Modules/Option/Resources/views/admin/options/index.blade.php
Normal file
@@ -0,0 +1,38 @@
|
||||
@extends('admin::layout')
|
||||
|
||||
@component('admin::components.page.header')
|
||||
@slot('title', trans('option::options.options'))
|
||||
|
||||
<li class="active">{{ trans('option::options.options') }}</li>
|
||||
@endcomponent
|
||||
|
||||
@component('admin::components.page.index_table')
|
||||
@slot('buttons', ['create'])
|
||||
@slot('resource', 'options')
|
||||
@slot('name', trans('option::options.option'))
|
||||
|
||||
@slot('thead')
|
||||
<tr>
|
||||
@include('admin::partials.table.select_all')
|
||||
|
||||
<th>{{ trans('admin::admin.table.id') }}</th>
|
||||
<th>{{ trans('option::options.table.name') }}</th>
|
||||
<th>{{ trans('option::options.table.type') }}</th>
|
||||
<th data-sort>{{ trans('admin::admin.table.created') }}</th>
|
||||
</tr>
|
||||
@endslot
|
||||
@endcomponent
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
new DataTable('#options-table .table', {
|
||||
columns: [
|
||||
{ data: 'checkbox', orderable: false, searchable: false, width: '3%' },
|
||||
{ data: 'id', width: '5%' },
|
||||
{ data: 'name', name: 'translations.name', orderable: false, defaultContent: '' },
|
||||
{ data: 'type', name: 'type' },
|
||||
{ data: 'created', name: 'created_at' },
|
||||
],
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@@ -0,0 +1,21 @@
|
||||
@push('globals')
|
||||
<script>
|
||||
FleetCart.data['option.values'] = {!! old_json('values', $option->values) !!};
|
||||
FleetCart.errors['option.values'] = @json($errors->get('values.*'), JSON_FORCE_OBJECT);
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@push('shortcuts')
|
||||
<dl class="dl-horizontal">
|
||||
<dt><code>b</code></dt>
|
||||
<dd>{{ trans('admin::admin.shortcuts.back_to_index', ['name' => trans('option::options.option')]) }}</dd>
|
||||
</dl>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
keypressAction([
|
||||
{ key: 'b', route: "{{ route('admin.options.index') }}" },
|
||||
]);
|
||||
</script>
|
||||
@endpush
|
||||
@@ -0,0 +1,73 @@
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
{{ Form::text('name', trans('option::attributes.name'), $errors, $option, ['required' => true]) }}
|
||||
|
||||
<div class="form-group required">
|
||||
<label for="type" class="col-md-3 control-label text-left">
|
||||
{{ trans('option::attributes.type') }}<span class="m-l-5 text-red">*</span>
|
||||
</label>
|
||||
|
||||
<div class="col-md-9">
|
||||
<select name="type" class="form-control custom-select-black" id="type">
|
||||
<option value="" {{ is_null(old('type', $option->type)) ? 'selected' : '' }}>
|
||||
{{ trans('option::options.form.option_types.please_select') }}
|
||||
</option>
|
||||
|
||||
<optgroup label="{{ trans('option::options.form.option_types.text') }}">
|
||||
<option value="field" {{ old('type', $option->type) === 'field' ? 'selected' : '' }}>
|
||||
{{ trans('option::options.form.option_types.field') }}
|
||||
</option>
|
||||
|
||||
<option value="textarea" {{ old('type', $option->type) === 'textarea' ? 'selected' : '' }}>
|
||||
{{ trans('option::options.form.option_types.textarea') }}
|
||||
</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="{{ trans('option::options.form.option_types.select') }}">
|
||||
<option value="dropdown" {{ old('type', $option->type) === 'dropdown' ? 'selected' : '' }}>
|
||||
{{ trans('option::options.form.option_types.dropdown') }}
|
||||
</option>
|
||||
|
||||
<option value="checkbox" {{ old('type', $option->type) === 'checkbox' ? 'selected' : '' }}>
|
||||
{{ trans('option::options.form.option_types.checkbox') }}
|
||||
</option>
|
||||
|
||||
<option value="checkbox_custom" {{ old('type', $option->type) === 'checkbox_custom' ? 'selected' : '' }}>
|
||||
{{ trans('option::options.form.option_types.checkbox_custom') }}
|
||||
</option>
|
||||
|
||||
<option value="radio" {{ old('type', $option->type) === 'radio' ? 'selected' : '' }}>
|
||||
{{ trans('option::options.form.option_types.radio') }}
|
||||
</option>
|
||||
|
||||
<option value="radio_custom" {{ old('type', $option->type) === 'radio_custom' ? 'selected' : '' }}>
|
||||
{{ trans('option::options.form.option_types.radio_custom') }}
|
||||
</option>
|
||||
|
||||
<option value="multiple_select" {{ old('type', $option->type) === 'multiple_select' ? 'selected' : '' }}>
|
||||
{{ trans('option::options.form.option_types.multiple_select') }}
|
||||
</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="{{ trans('option::options.form.option_types.date') }}">
|
||||
<option value="date" {{ old('type', $option->type) === 'date' ? 'selected' : '' }}>
|
||||
{{ trans('option::options.form.option_types.date') }}
|
||||
</option>
|
||||
|
||||
<option value="date_time" {{ old('type', $option->type) === 'date_time' ? 'selected' : '' }}>
|
||||
{{ trans('option::options.form.option_types.date_time') }}
|
||||
</option>
|
||||
|
||||
<option value="time" {{ old('type', $option->type) === 'time' ? 'selected' : '' }}>
|
||||
{{ trans('option::options.form.option_types.time') }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
|
||||
{!! $errors->first('type', '<span class="help-block text-red">:message</span>') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ Form::checkbox('is_required', trans('option::attributes.is_required'), trans('option::options.form.this_option_is_required'), $errors, $option) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="option-values clearfix" id="option-values">
|
||||
<div class="alert alert-info" id="option-values-info">
|
||||
{{ trans('option::options.please_select_a_option_type') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@include('option::admin.options.templates.option.text')
|
||||
@include('option::admin.options.templates.option.select')
|
||||
@include('option::admin.options.templates.option.select_values')
|
||||
@@ -0,0 +1,39 @@
|
||||
<script type="text/html" id="option-select-template">
|
||||
<div class="option-select <% if (optionId === undefined) { %> m-b-15 <% } %>">
|
||||
<div class="table-responsive">
|
||||
<table class="options table table-bordered table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{{ trans('option::attributes.label') }}</th>
|
||||
<th>{{ trans('option::attributes.price') }}</th>
|
||||
<th>{{ trans('option::attributes.price_type') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody
|
||||
<% if (optionId === undefined) { %>
|
||||
id="select-values"
|
||||
<% } else { %>
|
||||
id="option-<%- optionId %>-select-values"
|
||||
<% } %>
|
||||
>
|
||||
{{-- Custom option dropdown rows will be added here dynamically using JS --}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
<% if (optionId === undefined) { %>
|
||||
id="add-new-row"
|
||||
<% } else { %>
|
||||
id="option-<%- optionId %>-add-new-row"
|
||||
<% } %>
|
||||
>
|
||||
{{ trans('option::options.form.add_new_row') }}
|
||||
</button>
|
||||
</div>
|
||||
</script>
|
||||
@@ -0,0 +1,83 @@
|
||||
<script type="text/html" id="option-select-values-template">
|
||||
<tr class="option-row">
|
||||
<td class="text-center">
|
||||
<span class="drag-icon">
|
||||
<i class="fa"></i>
|
||||
<i class="fa"></i>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="hidden"
|
||||
<% if (optionId === undefined) { %>
|
||||
name="values[<%- valueId %>][id]"
|
||||
id="values-<%- valueId %>-id"
|
||||
<% } else { %>
|
||||
name="options[<%- optionId %>][values][<%- valueId %>][id]"
|
||||
id="option-<%- optionId %>-values-<%- valueId %>-id"
|
||||
<% } %>
|
||||
|
||||
value="<%- value.id %>"
|
||||
>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
<% if (optionId === undefined) { %>
|
||||
name="values[<%- valueId %>][label]"
|
||||
id="values-<%- valueId %>-label"
|
||||
<% } else { %>
|
||||
name="options[<%- optionId %>][values][<%- valueId %>][label]"
|
||||
id="option-<%- optionId %>-values-<%- valueId %>-label"
|
||||
<% } %>
|
||||
|
||||
class="form-control"
|
||||
value="<%- value.label %>"
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
<% if (optionId === undefined) { %>
|
||||
name="values[<%- valueId %>][price]"
|
||||
id="values-<%- valueId %>-price"
|
||||
<% } else { %>
|
||||
name="options[<%- optionId %>][values][<%- valueId %>][price]"
|
||||
id="option-<%- optionId %>-values-<%- valueId %>-price"
|
||||
<% } %>
|
||||
class="form-control"
|
||||
value="<%- _.isObject(value.price) ? value.price.amount : value.price %>"
|
||||
step="0.01"
|
||||
min="0"
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
<% if (optionId === undefined) { %>
|
||||
name="values[<%- valueId %>][price_type]"
|
||||
id="values-<%- valueId %>-price_type"
|
||||
<% } else { %>
|
||||
name="options[<%- optionId %>][values][<%- valueId %>][price_type]"
|
||||
id="option-<%- optionId %>-values-<%- valueId %>-price_type"
|
||||
<% } %>
|
||||
class="form-control custom-select-black"
|
||||
>
|
||||
<option value="fixed"
|
||||
<%= value.price_type === 'fixed' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.price_types.fixed') }}
|
||||
</option>
|
||||
|
||||
<option value="percent"
|
||||
<%= value.price_type === 'percent' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.price_types.percent') }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button type="button" class="btn btn-default delete-row" data-toggle="tooltip" title="{{ trans('option::options.form.delete_row') }}">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</td>
|
||||
</script>
|
||||
@@ -0,0 +1,54 @@
|
||||
<script type="text/html" id="option-text-template">
|
||||
<div class="table-responsive option-text">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ trans('option::attributes.price') }}</th>
|
||||
<th>{{ trans('option::attributes.price_type') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input
|
||||
type="number"
|
||||
<% if (optionId === undefined) { %>
|
||||
name="values[0][price]"
|
||||
id="values-0-price"
|
||||
<% } else { %>
|
||||
name="options[<%- optionId %>][values][0][price]"
|
||||
id="option-<%- optionId %>-values-0-price"
|
||||
<% } %>
|
||||
class="form-control"
|
||||
value="<%- _.isObject(value.price) ? value.price.amount : value.price %>"
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
<% if (optionId === undefined) { %>
|
||||
name="values[0][price_type]"
|
||||
id="values-0-price-type"
|
||||
<% } else { %>
|
||||
name="options[<%- optionId %>][values][0][price_type]"
|
||||
id="option-<%- optionId %>-values-0-price-type"
|
||||
<% } %>
|
||||
class="form-control custom-select-black"
|
||||
>
|
||||
<option value="fixed"
|
||||
<%= value.price_type === 'fixed' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.price_types.fixed') }}
|
||||
</option>
|
||||
|
||||
<option value="percent"
|
||||
<%= value.price_type === 'percent' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.price_types.percent') }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</script>
|
||||
@@ -0,0 +1,147 @@
|
||||
<script type="text/html" id="option-template">
|
||||
<div class="content-accordion panel-group options-group-wrapper" id="option-<%- optionId %>">
|
||||
<div class="panel panel-default option">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<a data-toggle="collapse" data-parent="#option-<%- optionId %>" href="#custom-collapse-<%- optionId %>">
|
||||
<span class="drag-icon pull-left">
|
||||
<i class="fa"></i>
|
||||
<i class="fa"></i>
|
||||
</span>
|
||||
|
||||
<span id="option-name" class="pull-left">{{ trans('option::options.form.new_option') }}</span>
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div id="custom-collapse-<%- optionId %>" class="panel-collapse collapse in">
|
||||
<div class="panel-body">
|
||||
<div class="new-option clearfix">
|
||||
<input type="hidden" name="options[<%- optionId %>][id]" value="<%- option.id %>">
|
||||
|
||||
<div class="col-lg-6 col-md-12 p-l-0">
|
||||
<div class="form-group">
|
||||
<label for="option-<%- optionId %>-name">{{ trans('option::attributes.name') }}</label>
|
||||
<input type="text" name="options[<%- optionId %>][name]" class="form-control option-name-field" id="option-<%- optionId %>-name" value="<%- option.name %>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-12 p-l-0">
|
||||
<div class="form-group">
|
||||
<label for="option-<%- optionId %>-type">{{ trans('option::attributes.type') }}</label>
|
||||
|
||||
<select name="options[<%- optionId %>][type]" class="form-control custom-select-black" id="option-<%- optionId %>-type">
|
||||
<option value=""
|
||||
<%= option.type === null ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.option_types.please_select') }}
|
||||
</option>
|
||||
|
||||
<optgroup label="{{ trans('option::options.form.option_types.text') }}">
|
||||
<option value="field"
|
||||
<%= option.type === 'field' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.option_types.field') }}
|
||||
</option>
|
||||
|
||||
<option value="textarea"
|
||||
<%= option.type === 'textarea' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.option_types.textarea') }}
|
||||
</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="{{ trans('option::options.form.option_types.select') }}">
|
||||
<option value="dropdown"
|
||||
<%= option.type === 'dropdown' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.option_types.dropdown') }}
|
||||
</option>
|
||||
|
||||
<option value="checkbox"
|
||||
<%= option.type === 'checkbox' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.option_types.checkbox') }}
|
||||
</option>
|
||||
|
||||
<option value="checkbox_custom"
|
||||
<%= option.type === 'checkbox_custom' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.option_types.checkbox_custom') }}
|
||||
</option>
|
||||
|
||||
<option value="radio"
|
||||
<%= option.type === 'radio' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.option_types.radio') }}
|
||||
</option>
|
||||
|
||||
<option value="radio_custom"
|
||||
<%= option.type === 'radio_custom' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.option_types.radio_custom') }}
|
||||
</option>
|
||||
|
||||
<option value="multiple_select"
|
||||
<%= option.type === 'multiple_select' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.option_types.multiple_select') }}
|
||||
</option>
|
||||
</optgroup>
|
||||
|
||||
<optgroup label="{{ trans('option::options.form.option_types.date') }}">
|
||||
<option value="date"
|
||||
<%= option.type === 'date' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.option_types.date') }}
|
||||
</option>
|
||||
|
||||
<option value="date_time"
|
||||
<%= option.type === 'date_time' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.option_types.date_time') }}
|
||||
</option>
|
||||
|
||||
<option value="time"
|
||||
<%= option.type === 'time' ? 'selected' : '' %>
|
||||
>
|
||||
{{ trans('option::options.form.option_types.time') }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<input type="hidden" name="options[<%- optionId %>][is_required]" value="0">
|
||||
|
||||
<input type="checkbox"
|
||||
name="options[<%- optionId %>][is_required]"
|
||||
class="form-control"
|
||||
id="option-<%- optionId %>-is-required"
|
||||
value="1"
|
||||
<%= option.is_required ? 'checked' : '' %>
|
||||
>
|
||||
|
||||
<label for="option-<%- optionId %>-is-required">{{ trans('option::attributes.is_required') }}</label>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-default delete-option pull-right" data-toggle="tooltip" title="{{ trans('option::options.form.delete_option') }}">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div class="option-values clearfix" id="option-<%- optionId %>-values">
|
||||
{{-- Custom option values will be added here dynamically using JS --}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
@include('option::admin.options.templates.option.text')
|
||||
@include('option::admin.options.templates.option.select')
|
||||
@include('option::admin.options.templates.option.select_values')
|
||||
@@ -0,0 +1,42 @@
|
||||
<div id="options-group">
|
||||
{{-- Options will be added here dynamically using JS --}}
|
||||
</div>
|
||||
|
||||
<div class="box-footer no-border p-t-0">
|
||||
<div class="form-group pull-left">
|
||||
<div class="col-md-10">
|
||||
<button type="button" class="btn btn-default m-r-10" id="add-new-option">
|
||||
{{ trans('option::options.form.add_new_option') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@hasAccess('admin.options.index')
|
||||
@if ($globalOptions->isNotEmpty())
|
||||
<div class="add-global-option clearfix pull-right">
|
||||
<div class="form-group pull-left">
|
||||
<select class="form-control custom-select-black" id="global-option">
|
||||
<option value="">{{ trans('option::options.select_global_option') }}</option>
|
||||
|
||||
@foreach ($globalOptions as $globalOption)
|
||||
<option value="{{ $globalOption->id }}">{{ $globalOption->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-default" id="add-global-option" data-loading>
|
||||
{{ trans('option::options.form.add_global_option') }}
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
@endHasAccess
|
||||
</div>
|
||||
|
||||
@push('globals')
|
||||
<script>
|
||||
FleetCart.data['product.options'] = {!! old_json('options', $product->options) !!};
|
||||
FleetCart.errors['product.options'] = @json($errors->get('options.*'), JSON_FORCE_OBJECT);
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@include('option::admin.options.templates.product_option')
|
||||
45
Modules/Option/Routes/admin.php
Normal file
45
Modules/Option/Routes/admin.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('options', [
|
||||
'as' => 'admin.options.index',
|
||||
'uses' => 'OptionController@index',
|
||||
'middleware' => 'can:admin.options.index',
|
||||
]);
|
||||
|
||||
Route::get('options/create', [
|
||||
'as' => 'admin.options.create',
|
||||
'uses' => 'OptionController@create',
|
||||
'middleware' => 'can:admin.options.create',
|
||||
]);
|
||||
|
||||
Route::post('options', [
|
||||
'as' => 'admin.options.store',
|
||||
'uses' => 'OptionController@store',
|
||||
'middleware' => 'can:admin.options.create',
|
||||
]);
|
||||
|
||||
Route::get('options/{id}', [
|
||||
'as' => 'admin.options.show',
|
||||
'uses' => 'OptionController@show',
|
||||
'middleware' => 'can:admin.options.edit',
|
||||
]);
|
||||
|
||||
Route::get('options/{id}/edit', [
|
||||
'as' => 'admin.options.edit',
|
||||
'uses' => 'OptionController@edit',
|
||||
'middleware' => 'can:admin.options.edit',
|
||||
]);
|
||||
|
||||
Route::put('options/{id}', [
|
||||
'as' => 'admin.options.update',
|
||||
'uses' => 'OptionController@update',
|
||||
'middleware' => 'can:admin.options.edit',
|
||||
]);
|
||||
|
||||
Route::delete('options/{ids}', [
|
||||
'as' => 'admin.options.destroy',
|
||||
'uses' => 'OptionController@destroy',
|
||||
'middleware' => 'can:admin.options.destroy',
|
||||
]);
|
||||
26
Modules/Option/Sidebar/SidebarExtender.php
Normal file
26
Modules/Option/Sidebar/SidebarExtender.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Option\Sidebar;
|
||||
|
||||
use Maatwebsite\Sidebar\Item;
|
||||
use Maatwebsite\Sidebar\Menu;
|
||||
use Maatwebsite\Sidebar\Group;
|
||||
use Modules\Admin\Sidebar\BaseSidebarExtender;
|
||||
|
||||
class SidebarExtender extends BaseSidebarExtender
|
||||
{
|
||||
public function extend(Menu $menu)
|
||||
{
|
||||
$menu->group(trans('admin::sidebar.content'), function (Group $group) {
|
||||
$group->item(trans('product::sidebar.products'), function (Item $item) {
|
||||
$item->item(trans('option::sidebar.options'), function (Item $item) {
|
||||
$item->weight(25);
|
||||
$item->route('admin.options.index');
|
||||
$item->authorize(
|
||||
$this->auth->hasAccess('admin.options.index')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
27
Modules/Option/composer.json
Normal file
27
Modules/Option/composer.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "fleetcart/option",
|
||||
"description": "The FleetCart Option Module.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Envay Soft",
|
||||
"email": "envaysoft@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.0.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Modules\\Option\\": ""
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
5
Modules/Option/helpers.php
Normal file
5
Modules/Option/helpers.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
if (! defined('FOR_SELECT_OPTION')) {
|
||||
define('FOR_SELECT_OPTION', true);
|
||||
}
|
||||
12
Modules/Option/module.json
Normal file
12
Modules/Option/module.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "Option",
|
||||
"alias": "option",
|
||||
"priority": 100,
|
||||
"providers": [
|
||||
"Modules\\Option\\Providers\\OptionServiceProvider",
|
||||
"Modules\\Option\\Providers\\EventServiceProvider"
|
||||
],
|
||||
"files": [
|
||||
"helpers.php"
|
||||
]
|
||||
}
|
||||
8
Modules/Option/webpack.mix.js
Normal file
8
Modules/Option/webpack.mix.js
Normal file
@@ -0,0 +1,8 @@
|
||||
let mix = require('laravel-mix');
|
||||
let execSync = require('child_process').execSync;
|
||||
|
||||
mix.js(`${__dirname}/Resources/assets/admin/js/main.js`, `${__dirname}/Assets/admin/js/option.js`)
|
||||
.sass(`${__dirname}/Resources/assets/admin/sass/main.scss`, `${__dirname}/Assets/admin/css/option.css`)
|
||||
.then(() => {
|
||||
execSync(`npm run rtlcss ${__dirname}/Assets/admin/css/option.css ${__dirname}/Assets/admin/css/option.rtl.css`);
|
||||
});
|
||||
Reference in New Issue
Block a user