first upload all files
This commit is contained in:
50
Modules/FlashSale/Admin/FlashSaleTabs.php
Normal file
50
Modules/FlashSale/Admin/FlashSaleTabs.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\FlashSale\Admin;
|
||||
|
||||
use Modules\Admin\Ui\Tab;
|
||||
use Modules\Admin\Ui\Tabs;
|
||||
|
||||
class FlashSaleTabs extends Tabs
|
||||
{
|
||||
/**
|
||||
* Indicate that submit button should add offset class.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $buttonOffset = false;
|
||||
|
||||
public function make()
|
||||
{
|
||||
$this->group('flash_sale_information', trans('flashsale::flash_sales.tabs.group.flash_sale_information'))
|
||||
->active()
|
||||
->add($this->products())
|
||||
->add($this->settings());
|
||||
}
|
||||
|
||||
private function products()
|
||||
{
|
||||
return tap(new Tab('products', trans('flashsale::flash_sales.tabs.products')), function (Tab $tab) {
|
||||
$tab->active();
|
||||
$tab->weight(5);
|
||||
|
||||
$tab->fields([
|
||||
'products.*.product_id',
|
||||
'products.*.end_date',
|
||||
'products.*.price',
|
||||
'products.*.quantity',
|
||||
]);
|
||||
|
||||
$tab->view('flashsale::admin.flash_sales.tabs.products');
|
||||
});
|
||||
}
|
||||
|
||||
private function settings()
|
||||
{
|
||||
return tap(new Tab('settings', trans('flashsale::flash_sales.tabs.settings')), function (Tab $tab) {
|
||||
$tab->weight(10);
|
||||
$tab->fields(['campaign_name']);
|
||||
$tab->view('flashsale::admin.flash_sales.tabs.settings');
|
||||
});
|
||||
}
|
||||
}
|
||||
21
Modules/FlashSale/Config/assets.php
Normal file
21
Modules/FlashSale/Config/assets.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Define which assets will be available through the asset manager
|
||||
|--------------------------------------------------------------------------
|
||||
| These assets are registered on the asset manager
|
||||
*/
|
||||
'all_assets' => [
|
||||
'admin.flashsale.js' => ['module' => 'flashsale:admin/js/flashsale.js'],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Define which default assets will always be included in your pages
|
||||
| through the asset pipeline
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'required_assets' => [],
|
||||
];
|
||||
10
Modules/FlashSale/Config/permissions.php
Normal file
10
Modules/FlashSale/Config/permissions.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'admin.flash_sales' => [
|
||||
'index' => 'flashsale::permissions.index',
|
||||
'create' => 'flashsale::permissions.create',
|
||||
'edit' => 'flashsale::permissions.edit',
|
||||
'destroy' => 'flashsale::permissions.destroy',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateFlashSalesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('flash_sales', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('flash_sales');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateFlashSaleTranslationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('flash_sale_translations', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('flash_sale_id')->unsigned();
|
||||
$table->string('locale');
|
||||
$table->string('campaign_name');
|
||||
|
||||
$table->unique(['flash_sale_id', 'locale']);
|
||||
$table->foreign('flash_sale_id')->references('id')->on('flash_sales')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('flash_sale_translations');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateFlashSaleProductsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('flash_sale_products', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->integer('flash_sale_id')->unsigned();
|
||||
$table->integer('product_id')->unsigned();
|
||||
$table->date('end_date');
|
||||
$table->decimal('price', 18, 4)->unsigned();
|
||||
$table->integer('qty');
|
||||
$table->integer('position');
|
||||
|
||||
$table->foreign('flash_sale_id')->references('id')->on('flash_sales')->onDelete('cascade');
|
||||
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('flash_sale_products');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateFlashSaleProductOrdersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('flash_sale_product_orders', function (Blueprint $table) {
|
||||
$table->integer('flash_sale_product_id')->unsigned();
|
||||
$table->integer('order_id')->unsigned();
|
||||
$table->integer('qty');
|
||||
|
||||
$table->primary(['flash_sale_product_id', 'order_id']);
|
||||
$table->foreign('flash_sale_product_id')->references('id')->on('flash_sale_products')->onDelete('cascade');
|
||||
$table->foreign('order_id')->references('id')->on('orders')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('flash_sale_product_orders');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\FlashSale\Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class FlashSaleDatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
Model::unguard();
|
||||
|
||||
// $this->call("OthersTableSeeder");
|
||||
}
|
||||
}
|
||||
147
Modules/FlashSale/Entities/FlashSale.php
Normal file
147
Modules/FlashSale/Entities/FlashSale.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\FlashSale\Entities;
|
||||
|
||||
use Modules\Support\Money;
|
||||
use Modules\Admin\Ui\AdminTable;
|
||||
use Modules\Support\Eloquent\Model;
|
||||
use Modules\Product\Entities\Product;
|
||||
use Modules\Support\Eloquent\Translatable;
|
||||
|
||||
class FlashSale extends Model
|
||||
{
|
||||
use Translatable;
|
||||
|
||||
/**
|
||||
* Active flash sale.
|
||||
*
|
||||
* @var self
|
||||
*/
|
||||
private static $active;
|
||||
|
||||
/**
|
||||
* The relations to eager load on every query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $with = ['translations'];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['id'];
|
||||
|
||||
/**
|
||||
* The attributes that are translatable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $translatedAttributes = ['campaign_name'];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::saved(function ($flashSale) {
|
||||
if (! empty(request()->has('products'))) {
|
||||
$flashSale->saveProducts(request('products'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active flash sale.
|
||||
*
|
||||
* @param int $id
|
||||
* @return self
|
||||
*/
|
||||
public static function active()
|
||||
{
|
||||
if (is_callable(self::$active)) {
|
||||
return call_user_func(self::$active);
|
||||
}
|
||||
|
||||
return new self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an active flash sale campaign.
|
||||
*
|
||||
* @param int $id
|
||||
* @return void
|
||||
*/
|
||||
public static function activeCampaign($id)
|
||||
{
|
||||
self::$active = function () use ($id) {
|
||||
return once(function () use ($id) {
|
||||
return self::withEligibleProducts()->where('id', $id)->firstOrNew([]);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
public static function contains(Product $product)
|
||||
{
|
||||
return self::active()->products->contains($product);
|
||||
}
|
||||
|
||||
public static function pivot(Product $product)
|
||||
{
|
||||
return self::active()->products->find($product)->pivot;
|
||||
}
|
||||
|
||||
public static function remainingQty(Product $product)
|
||||
{
|
||||
$flashSaleProduct = self::pivot($product);
|
||||
|
||||
return $flashSaleProduct->qty - $flashSaleProduct->sold;
|
||||
}
|
||||
|
||||
public function scopeWithEligibleProducts($query)
|
||||
{
|
||||
$query->with(['products' => function ($query) {
|
||||
$query->forCard()->wherePivot('end_date', '>', now());
|
||||
}]);
|
||||
}
|
||||
|
||||
public function products()
|
||||
{
|
||||
return $this->belongsToMany(Product::class, 'flash_sale_products')
|
||||
->using(FlashSaleProduct::class)
|
||||
->withPivot(['id', 'end_date', 'price', 'qty'])
|
||||
->orderBy('position');
|
||||
}
|
||||
|
||||
public function getPriceAttribute($price)
|
||||
{
|
||||
return Money::inDefaultCurrency($price);
|
||||
}
|
||||
|
||||
public function saveProducts($products)
|
||||
{
|
||||
$this->products()->sync(
|
||||
$this->buildProductPivots($products)
|
||||
);
|
||||
}
|
||||
|
||||
private function buildProductPivots($products)
|
||||
{
|
||||
return collect($products)->values()->mapWithKeys(function ($attributes, $index) {
|
||||
return [$attributes['product_id'] => [
|
||||
'end_date' => $attributes['end_date'],
|
||||
'price' => $attributes['price'],
|
||||
'qty' => $attributes['qty'],
|
||||
'position' => $index,
|
||||
]];
|
||||
});
|
||||
}
|
||||
|
||||
public function table()
|
||||
{
|
||||
return new AdminTable($this->query());
|
||||
}
|
||||
}
|
||||
56
Modules/FlashSale/Entities/FlashSaleProduct.php
Normal file
56
Modules/FlashSale/Entities/FlashSaleProduct.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\FlashSale\Entities;
|
||||
|
||||
use Modules\Support\Money;
|
||||
use Modules\Order\Entities\Order;
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
|
||||
class FlashSaleProduct extends Pivot
|
||||
{
|
||||
/**
|
||||
* Indicates if the IDs are auto-incrementing.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $incrementing = true;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'flash_sale_products';
|
||||
|
||||
/**
|
||||
* The attributes that should be mutated to dates.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = [
|
||||
'end_date',
|
||||
];
|
||||
|
||||
/**
|
||||
* The accessors to append to the model's array form.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $appends = ['sold'];
|
||||
|
||||
public function getSoldAttribute()
|
||||
{
|
||||
return $this->orders()->sum('qty');
|
||||
}
|
||||
|
||||
public function getPriceAttribute($price)
|
||||
{
|
||||
return Money::inDefaultCurrency($price);
|
||||
}
|
||||
|
||||
public function orders()
|
||||
{
|
||||
return $this->belongsToMany(Order::class, 'flash_sale_product_orders', 'flash_sale_product_id')
|
||||
->withPivot('qty');
|
||||
}
|
||||
}
|
||||
15
Modules/FlashSale/Entities/FlashSaleTranslation.php
Normal file
15
Modules/FlashSale/Entities/FlashSaleTranslation.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\FlashSale\Entities;
|
||||
|
||||
use Modules\Support\Eloquent\TranslationModel;
|
||||
|
||||
class FlashSaleTranslation extends TranslationModel
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['campaign_name'];
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\FlashSale\Http\Controllers\Admin;
|
||||
|
||||
use Modules\Admin\Traits\HasCrudActions;
|
||||
use Modules\FlashSale\Entities\FlashSale;
|
||||
use Modules\FlashSale\Http\Requests\SaveFlashSaleRequest;
|
||||
|
||||
class FlashSaleController
|
||||
{
|
||||
use HasCrudActions;
|
||||
|
||||
/**
|
||||
* Model for the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = FlashSale::class;
|
||||
|
||||
/**
|
||||
* Label of the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label = 'flashsale::flash_sales.flash_sale';
|
||||
|
||||
/**
|
||||
* View path of the resource.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $viewPath = 'flashsale::admin.flash_sales';
|
||||
|
||||
/**
|
||||
* Form requests for the resource.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $validation = SaveFlashSaleRequest::class;
|
||||
}
|
||||
32
Modules/FlashSale/Http/Requests/SaveFlashSaleRequest.php
Normal file
32
Modules/FlashSale/Http/Requests/SaveFlashSaleRequest.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\FlashSale\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Modules\Core\Http\Requests\Request;
|
||||
|
||||
class SaveFlashSaleRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Available attributes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $availableAttributes = 'flashsale::attributes';
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'campaign_name' => ['required'],
|
||||
'products.*.product_id' => ['required', Rule::exists('products', 'id')],
|
||||
'products.*.end_date' => ['required', 'date'],
|
||||
'products.*.price' => ['required', 'numeric', 'min:0', 'max:99999999999999'],
|
||||
'products.*.qty' => ['required', 'numeric', 'min:0'],
|
||||
];
|
||||
}
|
||||
}
|
||||
25
Modules/FlashSale/Providers/FlashSaleServiceProvider.php
Normal file
25
Modules/FlashSale/Providers/FlashSaleServiceProvider.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\FlashSale\Providers;
|
||||
|
||||
use Modules\Support\Traits\AddsAsset;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Admin\Ui\Facades\TabManager;
|
||||
use Modules\FlashSale\Admin\FlashSaleTabs;
|
||||
|
||||
class FlashSaleServiceProvider extends ServiceProvider
|
||||
{
|
||||
use AddsAsset;
|
||||
|
||||
/**
|
||||
* Bootstrap the application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
TabManager::register('flash_sales', FlashSaleTabs::class);
|
||||
|
||||
$this->addAdminAssets('admin.flash_sales.(create|edit)', ['admin.media.js', 'admin.flashsale.js']);
|
||||
}
|
||||
}
|
||||
65
Modules/FlashSale/Resources/assets/admin/js/FlashSale.js
Normal file
65
Modules/FlashSale/Resources/assets/admin/js/FlashSale.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import FlashSaleProduct from './FlashSaleProduct';
|
||||
|
||||
export default class {
|
||||
constructor() {
|
||||
this.productCount = 0;
|
||||
|
||||
this.addFlashSaleProducts(FleetCart.data['flash_sale.products']);
|
||||
|
||||
if (this.productCount === 0) {
|
||||
this.addProduct();
|
||||
}
|
||||
|
||||
this.addFlashSaleProductsError(FleetCart.errors['flash_sale.products']);
|
||||
|
||||
this.attachEventListeners();
|
||||
this.makeProductPanelsSortable();
|
||||
}
|
||||
|
||||
addFlashSaleProducts(products) {
|
||||
for (let attributes of products) {
|
||||
this.addProduct(attributes);
|
||||
}
|
||||
}
|
||||
|
||||
addProduct(attributes = {}) {
|
||||
let productTemplate = new FlashSaleProduct({ productPanelNumber: this.productCount++, product: attributes });
|
||||
|
||||
$('#products-wrapper').append(productTemplate.render());
|
||||
|
||||
window.admin.selectize();
|
||||
}
|
||||
|
||||
addFlashSaleProductsError(errors) {
|
||||
for (let key in errors) {
|
||||
let parent = this.getInputFieldForKey(key).parent();
|
||||
|
||||
parent.addClass('has-error');
|
||||
parent.append(`<span class="help-block">${errors[key][0]}</span>`);
|
||||
}
|
||||
}
|
||||
|
||||
getInputFieldForKey(key) {
|
||||
let keyParts = key.split('.');
|
||||
|
||||
// Replace all "_" to "-".
|
||||
keyParts = keyParts.map(k => {
|
||||
return k.split('_').join('-');
|
||||
});
|
||||
|
||||
return $(`#${keyParts.join('-')}`);
|
||||
}
|
||||
|
||||
attachEventListeners() {
|
||||
$('.add-product').on('click', () => {
|
||||
this.addProduct();
|
||||
});
|
||||
}
|
||||
|
||||
makeProductPanelsSortable() {
|
||||
Sortable.create(document.getElementById('products-wrapper'), {
|
||||
handle: '.drag-icon',
|
||||
animation: 150,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
export default class {
|
||||
constructor(data) {
|
||||
this.productPanelHtml = this.getProductPanelHtml(data);
|
||||
}
|
||||
|
||||
getProductPanelHtml(data) {
|
||||
data.product = this.normalizeAttributes(data.product);
|
||||
|
||||
let template = _.template($('#flash-sale-product-template').html());
|
||||
|
||||
return $(template(data));
|
||||
}
|
||||
|
||||
normalizeAttributes(product) {
|
||||
if ($.isEmptyObject(product)) {
|
||||
return { id: null, pivot: { campaign_end: null, price: { amount: null }, qty: null } };
|
||||
}
|
||||
|
||||
if (! $.isEmptyObject(FleetCart.errors['flash_sale.products'])) {
|
||||
return {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
pivot: { campaign_end: product.campaign_end, price: { amount: product.price }, qty: product.qty },
|
||||
};
|
||||
}
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.attachEventListeners();
|
||||
|
||||
window.admin.dateTimePicker(this.productPanelHtml.find('.datetime-picker'));
|
||||
|
||||
return this.productPanelHtml;
|
||||
}
|
||||
|
||||
attachEventListeners() {
|
||||
this.productPanelHtml.find('.delete-product-panel').on('click', () => {
|
||||
this.productPanelHtml.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
5
Modules/FlashSale/Resources/assets/admin/js/main.js
Normal file
5
Modules/FlashSale/Resources/assets/admin/js/main.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import FlashSale from './FlashSale';
|
||||
|
||||
new FlashSale();
|
||||
|
||||
window.admin.removeSubmitButtonOffsetOn(['#products']);
|
||||
15
Modules/FlashSale/Resources/lang/en/attributes.php
Normal file
15
Modules/FlashSale/Resources/lang/en/attributes.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'product' => 'Product',
|
||||
'end_date' => 'End Date',
|
||||
'price' => 'Price',
|
||||
'quantity' => 'Quantity',
|
||||
'campaign_name' => 'Campaign Name',
|
||||
|
||||
// validation
|
||||
'products.*.product_id' => 'Product',
|
||||
'products.*.end_date' => 'End Date',
|
||||
'products.*.price' => 'Price',
|
||||
'products.*.qty' => 'Quantity',
|
||||
];
|
||||
20
Modules/FlashSale/Resources/lang/en/flash_sales.php
Normal file
20
Modules/FlashSale/Resources/lang/en/flash_sales.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'flash_sale' => 'Flash Sale',
|
||||
'flash_sales' => 'Flash Sales',
|
||||
'table' => [
|
||||
'campaign_name' => 'Campaign Name',
|
||||
],
|
||||
'tabs' => [
|
||||
'group' => [
|
||||
'flash_sale_information' => 'Flash Sale Information',
|
||||
],
|
||||
'products' => 'Products',
|
||||
'settings' => 'Settings',
|
||||
],
|
||||
'form' => [
|
||||
'add_product' => 'Add Product',
|
||||
'flash_sale_product' => 'Flash Sale Product',
|
||||
],
|
||||
];
|
||||
8
Modules/FlashSale/Resources/lang/en/permissions.php
Normal file
8
Modules/FlashSale/Resources/lang/en/permissions.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'index' => 'Index Flash Sale',
|
||||
'create' => 'Create Flash Sale',
|
||||
'edit' => 'Edit Flash Sale',
|
||||
'destroy' => 'Delete Flash Sale',
|
||||
];
|
||||
@@ -0,0 +1,18 @@
|
||||
@extends('admin::layout')
|
||||
|
||||
@component('admin::components.page.header')
|
||||
@slot('title', trans('admin::resource.create', ['resource' => trans('flashsale::flash_sales.flash_sale')]))
|
||||
|
||||
<li><a href="{{ route('admin.flash_sales.index') }}">{{ trans('flashsale::flash_sales.flash_sales') }}</a></li>
|
||||
<li class="active">{{ trans('admin::resource.create', ['resource' => trans('flashsale::flash_sales.flash_sale')]) }}</li>
|
||||
@endcomponent
|
||||
|
||||
@section('content')
|
||||
<form method="POST" action="{{ route('admin.flash_sales.store') }}" class="form-horizontal" id="flash-sale-create-form" novalidate>
|
||||
{{ csrf_field() }}
|
||||
|
||||
{!! $tabs->render(compact('flashSale')) !!}
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@include('flashsale::admin.flash_sales.partials.shortcuts')
|
||||
@@ -0,0 +1,20 @@
|
||||
@extends('admin::layout')
|
||||
|
||||
@component('admin::components.page.header')
|
||||
@slot('title', trans('admin::resource.edit', ['resource' => trans('flashsale::flash_sales.flash_sale')]))
|
||||
@slot('subtitle', $flashSale->campaign_name)
|
||||
|
||||
<li><a href="{{ route('admin.flash_sales.index') }}">{{ trans('flashsale::flash_sales.flash_sales') }}</a></li>
|
||||
<li class="active">{{ trans('admin::resource.edit', ['resource' => trans('flashsale::flash_sales.flash_sale')]) }}</li>
|
||||
@endcomponent
|
||||
|
||||
@section('content')
|
||||
<form method="POST" action="{{ route('admin.flash_sales.update', $flashSale) }}" class="form-horizontal" id="flash-sale-edit-form" novalidate>
|
||||
{{ csrf_field() }}
|
||||
{{ method_field('put') }}
|
||||
|
||||
{!! $tabs->render(compact('flashSale')) !!}
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@include('flashsale::admin.flash_sales.partials.shortcuts')
|
||||
@@ -0,0 +1,38 @@
|
||||
@extends('admin::layout')
|
||||
|
||||
@component('admin::components.page.header')
|
||||
@slot('title', trans('flashsale::flash_sales.flash_sales'))
|
||||
|
||||
<li class="active">{{ trans('flashsale::flash_sales.flash_sales') }}</li>
|
||||
@endcomponent
|
||||
|
||||
@component('admin::components.page.index_table')
|
||||
@slot('buttons', ['create'])
|
||||
@slot('resource', 'flash_sales')
|
||||
@slot('name', trans('flashsale::flash_sales.flash_sale'))
|
||||
|
||||
@component('admin::components.table')
|
||||
@slot('thead')
|
||||
<tr>
|
||||
@include('admin::partials.table.select_all')
|
||||
|
||||
<th>{{ trans('admin::admin.table.id') }}</th>
|
||||
<th>{{ trans('flashsale::flash_sales.table.campaign_name') }}</th>
|
||||
<th data-sort>{{ trans('admin::admin.table.created') }}</th>
|
||||
</tr>
|
||||
@endslot
|
||||
@endcomponent
|
||||
@endcomponent
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
new DataTable('#flash_sales-table .table', {
|
||||
columns: [
|
||||
{ data: 'checkbox', orderable: false, searchable: false, width: '3%' },
|
||||
{ data: 'id', width: '5%' },
|
||||
{ data: 'campaign_name', name: 'translations.campaign_name', orderable: false, defaultContent: '' },
|
||||
{ data: 'created', name: 'created_at' },
|
||||
],
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@@ -0,0 +1,14 @@
|
||||
@push('shortcuts')
|
||||
<dl class="dl-horizontal">
|
||||
<dt><code>b</code></dt>
|
||||
<dd>{{ trans('admin::admin.shortcuts.back_to_index', ['name' => trans('flashsale::flash_sales.flash_sale')]) }}</dd>
|
||||
</dl>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
keypressAction([
|
||||
{ key: 'b', route: "{{ route('admin.flash_sales.index') }}" }
|
||||
]);
|
||||
</script>
|
||||
@endpush
|
||||
@@ -0,0 +1,19 @@
|
||||
<div class="panel-wrap flash-sale" id="products-wrapper">
|
||||
{{-- Products will be added here dynamically using JS --}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="button" class="add-product btn btn-default m-l-15">
|
||||
{{ trans('flashsale::flash_sales.form.add_product') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@include('admin::partials.selectize_remote')
|
||||
@include('flashsale::admin.flash_sales.templates.product')
|
||||
|
||||
@push('globals')
|
||||
<script>
|
||||
FleetCart.data['flash_sale.products'] = {!! old_json('products', $flashSale->products) !!};
|
||||
FleetCart.errors['flash_sale.products'] = @json($errors->get('products.*'), JSON_FORCE_OBJECT);
|
||||
</script>
|
||||
@endpush
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
{{ Form::text('campaign_name', trans('flashsale::attributes.campaign_name'), $errors, $flashSale, ['required' => true]) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,93 @@
|
||||
<script type="text/html" id="flash-sale-product-template">
|
||||
<div class="panel">
|
||||
<div class="panel-header clearfix">
|
||||
<span class="drag-icon pull-left">
|
||||
<i class="fa"></i>
|
||||
<i class="fa"></i>
|
||||
</span>
|
||||
|
||||
{{ trans('flashsale::flash_sales.form.flash_sale_product') }}
|
||||
|
||||
<button type="button" class="delete-product-panel btn pull-right">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="products-<%- productPanelNumber %>-product-id">
|
||||
{{ trans('flashsale::attributes.product') }}<span class="m-l-5 text-red">*</span>
|
||||
</label>
|
||||
|
||||
<input type="hidden"
|
||||
name="products[<%- productPanelNumber %>][name]"
|
||||
class="form-control"
|
||||
id="products-<%- productPanelNumber %>-name"
|
||||
value="<%- product.name %>"
|
||||
>
|
||||
|
||||
<select name="products[<%- productPanelNumber %>][product_id]"
|
||||
class="form-control selectize prevent-creation"
|
||||
id="products-<%- productPanelNumber %>-product-id"
|
||||
data-url="{{ route('admin.products.index') }}"
|
||||
>
|
||||
<% if (product.id !== null && product.name !== null) { %>
|
||||
<option value="<%- product.id %>"><%- product.name %></option>
|
||||
<% } %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-xs-12">
|
||||
<div class="form-group">
|
||||
<label for="products-<%- productPanelNumber %>-campaign-end">
|
||||
{{ trans('flashsale::attributes.end_date') }}<span class="m-l-5 text-red">*</span>
|
||||
</label>
|
||||
|
||||
<input type="text"
|
||||
name="products[<%- productPanelNumber %>][end_date]"
|
||||
class="form-control datetime-picker"
|
||||
id="products-<%- productPanelNumber %>-campaign-end"
|
||||
value="<%- product.pivot.end_date %>"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-xs-6">
|
||||
<div class="form-group">
|
||||
<label for="products-<%- productPanelNumber %>-price">
|
||||
{{ trans('flashsale::attributes.price') }}<span class="m-l-5 text-red">*</span>
|
||||
</label>
|
||||
|
||||
<input type="number"
|
||||
name="products[<%- productPanelNumber %>][price]"
|
||||
class="form-control"
|
||||
id="products-<%- productPanelNumber %>-price"
|
||||
value="<%- product.pivot.price.amount %>"
|
||||
min="0"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-3 col-xs-6">
|
||||
<div class="form-group">
|
||||
<label for="products-<%- productPanelNumber %>-qty">
|
||||
{{ trans('flashsale::attributes.quantity') }}<span class="m-l-5 text-red">*</span>
|
||||
</label>
|
||||
|
||||
<input type="number"
|
||||
name="products[<%- productPanelNumber %>][qty]"
|
||||
class="form-control"
|
||||
id="products-<%- productPanelNumber %>-qty"
|
||||
value="<%- product.pivot.qty %>"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
39
Modules/FlashSale/Routes/admin.php
Normal file
39
Modules/FlashSale/Routes/admin.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('flash-sales', [
|
||||
'as' => 'admin.flash_sales.index',
|
||||
'uses' => 'FlashSaleController@index',
|
||||
'middleware' => 'can:admin.flash_sales.index',
|
||||
]);
|
||||
|
||||
Route::get('flash-sales/create', [
|
||||
'as' => 'admin.flash_sales.create',
|
||||
'uses' => 'FlashSaleController@create',
|
||||
'middleware' => 'can:admin.flash_sales.create',
|
||||
]);
|
||||
|
||||
Route::post('flash-sales', [
|
||||
'as' => 'admin.flash_sales.store',
|
||||
'uses' => 'FlashSaleController@store',
|
||||
'middleware' => 'can:admin.flash_sales.create',
|
||||
]);
|
||||
|
||||
Route::get('flash-sales/{id}/edit', [
|
||||
'as' => 'admin.flash_sales.edit',
|
||||
'uses' => 'FlashSaleController@edit',
|
||||
'middleware' => 'can:admin.flash_sales.edit',
|
||||
]);
|
||||
|
||||
Route::put('flash-sales/{id}', [
|
||||
'as' => 'admin.flash_sales.update',
|
||||
'uses' => 'FlashSaleController@update',
|
||||
'middleware' => 'can:admin.flash_sales.edit',
|
||||
]);
|
||||
|
||||
Route::delete('flash-sales/{ids?}', [
|
||||
'as' => 'admin.flash_sales.destroy',
|
||||
'uses' => 'FlashSaleController@destroy',
|
||||
'middleware' => 'can:admin.flash_sales.destroy',
|
||||
]);
|
||||
25
Modules/FlashSale/Sidebar/SidebarExtender.php
Normal file
25
Modules/FlashSale/Sidebar/SidebarExtender.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\FlashSale\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('flashsale::flash_sales.flash_sales'), function (Item $item) {
|
||||
$item->icon('fa fa-bolt');
|
||||
$item->weight(17);
|
||||
$item->route('admin.flash_sales.index');
|
||||
$item->authorize(
|
||||
$this->auth->hasAccess('admin.flash_sales.index')
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
27
Modules/FlashSale/composer.json
Normal file
27
Modules/FlashSale/composer.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "fleetcart/flashsale",
|
||||
"description": "The FleetCart FlashSale Module.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Envay Soft",
|
||||
"email": "envaysoft@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.0.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Modules\\FlashSale\\": ""
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
9
Modules/FlashSale/module.json
Normal file
9
Modules/FlashSale/module.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "FlashSale",
|
||||
"alias": "flashsale",
|
||||
"description": "The FleetCart FlashSale Module.",
|
||||
"priority": 100,
|
||||
"providers": [
|
||||
"Modules\\FlashSale\\Providers\\FlashSaleServiceProvider"
|
||||
]
|
||||
}
|
||||
3
Modules/FlashSale/webpack.mix.js
Normal file
3
Modules/FlashSale/webpack.mix.js
Normal file
@@ -0,0 +1,3 @@
|
||||
let mix = require('laravel-mix');
|
||||
|
||||
mix.js(`${__dirname}/Resources/assets/admin/js/main.js`, `${__dirname}/Assets/admin/js/flashsale.js`);
|
||||
Reference in New Issue
Block a user