first upload all files

This commit is contained in:
NW
2023-06-11 13:14:03 +01:00
parent f14dbc52b5
commit c08b36d1b6
1705 changed files with 106852 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
<?php
namespace Modules\Account\Http\Controllers;
use Modules\Support\Country;
use Illuminate\Routing\Controller;
use Modules\Address\Entities\Address;
use Modules\Account\Http\Requests\SaveAddressRequest;
class AccountAddressController extends Controller
{
public function index()
{
return view('public.account.addresses.index', [
'addresses' => auth()->user()->addresses->keyBy('id'),
'defaultAddress' => auth()->user()->defaultAddress,
'countries' => Country::supported(),
]);
}
public function store(SaveAddressRequest $request)
{
$address = auth()->user()->addresses()->create($request->all());
return response()->json([
'address' => $address,
'message' => trans('account::messages.address_saved'),
]);
}
public function update(SaveAddressRequest $request, $id)
{
$address = Address::find($id);
$address->update($request->all());
return response()->json([
'address' => $address,
'message' => trans('account::messages.address_saved'),
]);
}
public function destroy($id)
{
auth()->user()->addresses()->find($id)->delete();
return response()->json([
'message' => trans('account::messages.address_deleted'),
]);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Modules\Account\Http\Controllers;
class AccountDashboardController
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view('public.account.dashboard.index', [
'account' => auth()->user(),
'recentOrders' => auth()->user()->recentOrders(5),
]);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Modules\Account\Http\Controllers;
use Illuminate\Routing\Controller;
use Modules\Address\Entities\DefaultAddress;
class AccountDefaultAddressController extends Controller
{
/**
* Update the specified resource in storage.
*
* @return \Illuminate\Contracts\Support\Renderable
*/
public function update()
{
DefaultAddress::updateOrCreate(
['customer_id' => auth()->id()],
['address_id' => request('address_id')]
);
return trans('account::messages.default_address_updated');
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Modules\Account\Http\Controllers;
use Modules\Order\Entities\Order;
class AccountDownloadsController
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view('public.account.downloads.index', [
'downloads' => $this->getDownloads(),
]);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$file = $this->getDownloads()->firstWhere('id', decrypt($id));
if (is_null($file) || ! file_exists($file->realPath())) {
return back()->with('error', trans('storefront::account.downloads.no_file_found'));
}
return response()->download($file->realPath(), $file->filename);
}
private function getDownloads()
{
return auth()->user()
->orders()
->with('downloads')
->where('status', Order::COMPLETED)
->latest()
->get()
->pluck('downloads.*.file')
->flatten()
->unique('id');
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Modules\Account\Http\Controllers;
class AccountOrdersController
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$orders = auth()->user()
->orders()
->latest()
->paginate(20);
return view('public.account.orders.index', compact('orders'));
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$order = auth()->user()
->orders()
->with(['products', 'coupon', 'taxes'])
->where('id', $id)
->firstOrFail();
return view('public.account.orders.show', compact('order'));
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Modules\Account\Http\Controllers;
use Modules\User\Http\Requests\UpdateProfileRequest;
class AccountProfileController
{
/**
* Show the form for editing the specified resource.
*
* @return \Illuminate\Http\Response
*/
public function edit()
{
return view('public.account.profile.edit', [
'account' => auth()->user(),
]);
}
/**
* Update the specified resource in storage.
*
* @param \Modules\User\Http\Requests\UpdateProfileRequest $request
* @return \Illuminate\Http\Response
*/
public function update(UpdateProfileRequest $request)
{
$request->bcryptPassword($request);
auth()->user()->update($request->all());
return back()->with('success', trans('account::messages.profile_updated'));
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Modules\Account\Http\Controllers;
class AccountReviewController
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$reviews = auth()->user()
->reviews()
->withoutGlobalScope('approved')
->with('product.files')
->whereHas('product')
->paginate(20);
return view('public.account.reviews.index', compact('reviews'));
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Modules\Account\Http\Controllers;
class AccountWishlistController
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view('public.account.wishlist.index');
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Modules\Account\Http\Requests;
use Modules\Core\Http\Requests\Request;
class SaveAddressRequest extends Request
{
/**
* Available attributes.
*
* @var string
*/
protected $availableAttributes = 'account::attributes.addresses';
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'first_name' => ['required'],
'last_name' => ['required'],
'address_1' => ['required'],
'city' => ['required'],
'zip' => ['required'],
'country' => ['required'],
'state' => ['required'],
];
}
}

View File

@@ -0,0 +1,13 @@
<?php
return [
'addresses' => [
'first_name' => 'First Name',
'last_name' => 'Last Name',
'address_1' => 'Address Line 1',
'city' => 'City',
'zip' => 'Postcode / ZIP',
'country' => 'Country',
'state' => 'State / Province',
],
];

View File

@@ -0,0 +1,8 @@
<?php
return [
'profile_updated' => 'Your profile has been updated.',
'default_address_updated' => 'The default address has been updated.',
'address_saved' => 'The address has been saved.',
'address_deleted' => 'The address has been deleted.',
];

View File

@@ -0,0 +1,27 @@
<?php
use Illuminate\Support\Facades\Route;
Route::middleware('auth')->group(function () {
Route::get('account', 'AccountDashboardController@index')->name('account.dashboard.index');
Route::get('account/profile', 'AccountProfileController@edit')->name('account.profile.edit');
Route::put('account/profile', 'AccountProfileController@update')->name('account.profile.update');
Route::get('account/orders', 'AccountOrdersController@index')->name('account.orders.index');
Route::get('account/orders/{id}', 'AccountOrdersController@show')->name('account.orders.show');
Route::get('account/downloads', 'AccountDownloadsController@index')->name('account.downloads.index');
Route::get('account/downloads/{id}', 'AccountDownloadsController@show')->name('account.downloads.show');
Route::get('account/wishlist', 'AccountWishlistController@index')->name('account.wishlist.index');
Route::get('account/reviews', 'AccountReviewController@index')->name('account.reviews.index');
Route::get('addresses', 'AccountAddressController@index')->name('account.addresses.index');
Route::post('addresses', 'AccountAddressController@store')->name('account.addresses.store');
Route::put('addresses/{id}', 'AccountAddressController@update')->name('account.addresses.update');
Route::delete('addresses/{id}', 'AccountAddressController@destroy')->name('account.addresses.destroy');
Route::post('addresses/change-default-address', 'AccountDefaultAddressController@update')->name('account.change_default_address');
});

View File

@@ -0,0 +1,27 @@
{
"name": "fleetcart/account",
"description": "The FleetCart Account Module.",
"authors": [
{
"name": "Envay Soft",
"email": "envaysoft@gmail.com"
}
],
"require": {
"php": "^8.0.2"
},
"autoload": {
"psr-4": {
"Modules\\Account\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,6 @@
{
"name": "Account",
"alias": "account",
"description": "The FleetCart Account Module.",
"priority": 100
}

View File

@@ -0,0 +1,42 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateAddressesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('addresses', function (Blueprint $table) {
$table->increments('id');
$table->integer('customer_id')->unsigned();
$table->string('first_name');
$table->string('last_name');
$table->string('address_1');
$table->string('address_2')->nullable();
$table->string('city');
$table->string('state');
$table->string('zip');
$table->string('country');
$table->timestamps();
$table->foreign('customer_id')->references('id')->on('users')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('addresses');
}
}

View File

@@ -0,0 +1,35 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateDefaultAddressesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('default_addresses', function (Blueprint $table) {
$table->increments('id');
$table->integer('customer_id')->unsigned();
$table->integer('address_id')->unsigned();
$table->foreign('customer_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('address_id')->references('id')->on('addresses')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('default_addresses');
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Modules\Address\Entities;
use Modules\Support\State;
use Modules\Support\Country;
use Modules\User\Entities\User;
use Modules\Support\Eloquent\Model;
class Address extends Model
{
protected $fillable = ['first_name', 'last_name', 'address_1', 'address_2', 'city', 'state', 'zip', 'country'];
protected $appends = ['full_name', 'state_name', 'country_name'];
public function customer()
{
return $this->belongsTo(User::class);
}
public function getFullNameAttribute()
{
return $this->first_name . ' ' . $this->last_name;
}
public function getStateNameAttribute()
{
return State::name($this->country, $this->state);
}
public function getCountryNameAttribute()
{
return Country::name($this->country);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Modules\Address\Entities;
use Illuminate\Database\Eloquent\Model;
class DefaultAddress extends Model
{
protected $with = ['address'];
protected $fillable = ['customer_id', 'address_id'];
public $timestamps = false;
public function address()
{
return $this->belongsTo(Address::class);
}
public function getAddress1Attribute()
{
return $this->address->address_1;
}
public function getAddress2Attribute()
{
return $this->address->address_1;
}
public function getCityAttribute()
{
return $this->address->city;
}
public function getStateAttribute()
{
return $this->address->state;
}
public function getZipAttribute()
{
return $this->address->zip;
}
public function getCountryAttribute()
{
return $this->address->country;
}
}

View File

@@ -0,0 +1,27 @@
{
"name": "fleetcart/address",
"description": "The FleetCart Address Module.",
"authors": [
{
"name": "Envay Soft",
"email": "envaysoft@gmail.com"
}
],
"require": {
"php": "^8.0.2"
},
"autoload": {
"psr-4": {
"Modules\\Address\\": ""
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,6 @@
{
"name": "Address",
"alias": "address",
"description": "The FleetCart Address Module.",
"priority": 100
}

View File

@@ -0,0 +1,25 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Define which assets will be available through the asset manager
|--------------------------------------------------------------------------
| These assets are registered on the asset manager
*/
'all_assets' => [
'admin.css' => ['module' => 'admin:css/admin.css'],
'admin.js' => ['module' => 'admin:js/admin.js'],
'admin.dashboard.css' => ['module' => 'admin:css/dashboard.css'],
'admin.dashboard.js' => ['module' => 'admin:js/dashboard.js'],
'admin.polyfill.js' => ['cdn' => 'https://cdn.polyfill.io/v2/polyfill.min.js'],
],
/*
|--------------------------------------------------------------------------
| Define which default assets will always be included in your pages
| through the asset pipeline
|--------------------------------------------------------------------------
*/
'required_assets' => ['admin.css', 'admin.polyfill.js', 'admin.js'],
];

View File

@@ -0,0 +1,66 @@
<?php
namespace Modules\Admin\Http\Controllers\Admin;
use Modules\User\Entities\User;
use Modules\Order\Entities\Order;
use Modules\Review\Entities\Review;
use Modules\Product\Entities\Product;
use Modules\Product\Entities\SearchTerm;
class DashboardController
{
/**
* Display the dashboard with its widgets.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view('admin::dashboard.index', [
'totalSales' => Order::totalSales(),
'totalOrders' => Order::withoutCanceledOrders()->count(),
'totalProducts' => Product::withoutGlobalScope('active')->count(),
'totalCustomers' => User::totalCustomers(),
'latestSearchTerms' => $this->getLatestSearchTerms(),
'latestOrders' => $this->getLatestOrders(),
'latestReviews' => $this->getLatestReviews(),
]);
}
private function getLatestSearchTerms()
{
return SearchTerm::latest('updated_at')->take(5)->get();
}
/**
* Get latest five orders.
*
* @return \Illuminate\Database\Eloquent\Collection
*/
private function getLatestOrders()
{
return Order::select([
'id',
'customer_first_name',
'customer_last_name',
'total',
'status',
'created_at',
])->latest()->take(5)->get();
}
/**
* Get latest five reviews.
*
* @return \Illuminate\Database\Eloquent\Collection
*/
private function getLatestReviews()
{
return Review::select('id', 'product_id', 'reviewer_name', 'rating')
->has('product')
->with('product:id')
->limit(5)
->get();
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Modules\Admin\Http\Controllers\Admin;
use Modules\Order\Entities\Order;
class SalesAnalyticsController
{
/**
* Display a listing of the resource.
*
* @param \Modules\Order\Entities\Order $order
* @return \Illuminate\Http\Response
*/
public function index(Order $order)
{
return response()->json([
'labels' => trans('admin::dashboard.sales_analytics.day_names'),
'data' => $order->salesAnalytics(),
]);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Modules\Admin\Http\ViewComposers;
use Modules\Core\Events\CollectingAssets;
use Modules\Core\Foundation\Asset\Pipeline\AssetPipeline;
class AssetsComposer
{
/**
* The instance of AssetPipeline.
*
* @var \Modules\Core\Foundation\Asset\Pipeline\AssetPipeline
*/
private $assetPipeline;
/**
* Create a new composer instance.
*
* @param \Modules\Core\Foundation\Asset\Pipeline\AssetPipeline $assetPipeline
*/
public function __construct(AssetPipeline $assetPipeline)
{
$this->assetPipeline = $assetPipeline;
}
/**
* Bind data to the view.
*
* @param \Illuminate\View\View $view
* @return void
*/
public function compose($view)
{
event(new CollectingAssets($this->assetPipeline));
$view->with('assets', $this->assetPipeline);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Modules\Admin\Http\ViewCreators;
use Illuminate\View\View;
use Modules\Admin\Sidebar\AdminSidebar;
use Maatwebsite\Sidebar\Presentation\SidebarRenderer;
class AdminSidebarCreator
{
/**
* @var \Modules\Admin\Sidebar\AdminSidebar
*/
protected $sidebar;
/**
* @var \Maatwebsite\Sidebar\Presentation\SidebarRenderer
*/
protected $renderer;
/**
* @param \Modules\Admin\Sidebar\AdminSidebar $sidebar
* @param \Maatwebsite\Sidebar\Presentation\SidebarRenderer $renderer
*/
public function __construct(AdminSidebar $sidebar, SidebarRenderer $renderer)
{
$this->sidebar = $sidebar;
$this->renderer = $renderer;
}
public function create(View $view)
{
$view->sidebar = $this->renderer->render($this->sidebar);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Modules\Admin\Providers;
use Modules\Admin\Ui\Facades\Form;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\View;
use Modules\Support\Traits\AddsAsset;
use Illuminate\Foundation\AliasLoader;
use Illuminate\Support\ServiceProvider;
use Modules\Admin\Http\ViewComposers\AssetsComposer;
class AdminServiceProvider extends ServiceProvider
{
use AddsAsset;
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
View::composer('admin::layout', AssetsComposer::class);
Paginator::defaultSimpleView('admin::pagination.simple');
$this->addAdminAssets('admin.dashboard.index', ['admin.dashboard.css', 'admin.dashboard.js']);
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
AliasLoader::getInstance()->alias('Form', Form::class);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Modules\Admin\Providers;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Maatwebsite\Sidebar\SidebarManager;
use Modules\Admin\Sidebar\AdminSidebar;
use Modules\Admin\Http\ViewCreators\AdminSidebarCreator;
class SidebarServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot(SidebarManager $manager)
{
if (! config('app.installed')) {
return;
}
if ($this->app['inAdminPanel']) {
$manager->register(AdminSidebar::class);
}
View::creator('admin::partials.sidebar', AdminSidebarCreator::class);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

View File

@@ -0,0 +1,176 @@
import NProgress from 'nprogress';
export default class {
constructor() {
this.selectize();
this.dateTimePicker();
this.changeAccordionTabState();
this.preventChangingCurrentTab();
this.buttonLoading();
this.confirmationModal();
this.tooltip();
this.shortcuts();
this.nprogress();
}
selectize() {
let selects = $('select.selectize').removeClass('form-control custom-select-black');
let options = _.merge({
valueField: 'id',
labelField: 'name',
searchField: 'name',
delimiter: ',',
persist: true,
selectOnTab: true,
hideSelected: false,
allowEmptyOption: true,
onItemAdd(value) {
this.getItem(value)[0].innerHTML = this.getItem(value)[0].innerHTML.replace(/¦––\s/g, '');
},
onInitialize() {
for (let index in this.options) {
let label = this.options[index].name;
let value = this.options[index].id;
this.$control.find(`.item[data-value="${value}"]`).html(
label.replace(/¦––\s/g, '') +
'<a href="javascript:void(0)" class="remove" tabindex="-1">×</a>'
);
}
},
}, ...FleetCart.selectize);
for (let select of selects) {
select = $(select);
let create = true;
let plugins = ['remove_button', 'restore_on_backspace'];
if (select.hasClass('prevent-creation')) {
create = false;
plugins.remove('restore_on_backspace');
}
select.selectize(_.merge(options, { create, plugins }));
}
}
dateTimePicker(elements) {
elements = elements || $('.datetime-picker');
elements = elements instanceof jQuery ? elements : $(elements);
for (let el of elements) {
$(el).flatpickr({
mode: el.hasAttribute('data-range') ? 'range' : 'single',
enableTime: el.hasAttribute('data-time'),
noCalender: el.hasAttribute('data-no-calender'),
altInput: true,
});
}
}
changeAccordionTabState() {
$('.accordion-box [data-toggle="tab"]').on('click', (e) => {
if (! $(e.currentTarget).parent().hasClass('active')) {
$('.accordion-tab li.active').removeClass('active');
}
});
}
preventChangingCurrentTab() {
$('[data-toggle="tab"]').on('click', (e) => {
let targetElement = $(e.currentTarget);
if (targetElement.parent().hasClass('active')) {
return false;
}
});
}
removeSubmitButtonOffsetOn(tabs, tabsSelector = null) {
tabs = Array.isArray(tabs) ? tabs : [tabs];
$(tabsSelector || '.accordion-tab li > a').on('click', (e) => {
if (tabs.includes(e.currentTarget.getAttribute('href'))) {
setTimeout(() => {
$('button[type=submit]').parent().removeClass('col-md-offset-2');
}, 150);
} else {
setTimeout(() => {
$('button[type=submit]').parent().addClass('col-md-offset-2');
}, 150);
}
});
}
buttonLoading() {
$(document).on('click', '[data-loading]', (e) => {
let button = $(e.currentTarget);
button.data('loading-text', button.html())
.addClass('btn-loading')
.button('loading');
});
}
stopButtonLoading(button) {
button = button instanceof jQuery ? button : $(button);
button.data('loading-text', button.html())
.removeClass('btn-loading')
.button('reset');
}
confirmationModal() {
let confirmationModal = $('#confirmation-modal');
$('[data-confirm]').on('click', () => {
confirmationModal.modal('show');
});
confirmationModal.find('form').on('submit', () => {
confirmationModal.find('button.delete').prop('disabled', true);
});
confirmationModal.on('hidden.bs.modal', () => {
confirmationModal.find('button.delete').prop('disabled', false);
});
confirmationModal.on('shown.bs.modal', () => {
confirmationModal.find('button.delete').focus();
});
}
tooltip() {
$('[data-toggle="tooltip"]').tooltip({ trigger : 'hover' })
.on('click', (e) => {
$(e.currentTarget).tooltip('hide');
});
}
shortcuts() {
Mousetrap.bind('f1', () => {
window.open(`http://envaysoft.com/fleetcart/docs/${FleetCart.version}`, '_blank');
});
Mousetrap.bind('?', () => {
$('#keyboard-shortcuts-modal').modal();
});
}
nprogress() {
let inMobile = /iphone|ipod|android|ie|blackberry|fennec/i.test(window.navigator.userAgent);
if (inMobile) {
return;
}
NProgress.configure({ showSpinner: false });
$(document).ajaxStart(() => NProgress.start());
$(document).ajaxComplete(() => NProgress.done());
}
}

View File

@@ -0,0 +1,281 @@
// Initialize state holders.
FleetCart.dataTable = { routes: {}, selected: {} };
export default class {
constructor(selector, options, callback) {
this.selector = selector;
this.element = $(selector);
if (FleetCart.dataTable.selected[selector] === undefined) {
FleetCart.dataTable.selected[selector] = [];
}
this.initiateDataTable(options, callback);
this.addErrorHandler();
this.registerTableProcessingPlugin();
}
initiateDataTable(options, callback) {
let sortColumn = this.element.find("th[data-sort]");
this.element.dataTable(
_.merge(
{
serverSide: true,
processing: true,
ajax: this.route("index", { table: true }),
stateSave: true,
sort: true,
info: true,
filter: true,
lengthChange: true,
paginate: true,
autoWidth: false,
pageLength: 20,
lengthMenu: [10, 20, 50, 100, 200],
language: {
processing: '<i class="fa fa-refresh fa-spin"></i>',
},
order: [
sortColumn.index() !== -1 ? sortColumn.index() : 1,
sortColumn.data("sort") || "desc",
],
initComplete: () => {
if (this.hasRoute("destroy")) {
let deleteButton = this.addTableActions();
deleteButton.on("click", () => this.deleteRows());
this.selectAllRowsEventListener();
}
if (this.hasRoute("show") || this.hasRoute("edit")) {
this.onRowClick(this.redirectToRowPage);
}
if (callback !== undefined) {
callback.call(this);
}
},
rowCallback: (row, data) => {
if (this.hasRoute("show") || this.hasRoute("edit")) {
this.makeRowClickable(row, data.id);
}
},
drawCallback: () => {
this.element.find(".select-all").prop("checked", false);
setTimeout(() => {
this.selectRowEventListener();
this.checkSelectedCheckboxes(
this.constructor.getSelectedIds(this.selector)
);
});
},
stateSaveParams(settings, data) {
delete data.start;
delete data.search;
},
},
options
)
);
}
addTableActions() {
let button = `
<button type="button" class="btn btn-default btn-delete">
${trans("admin::admin.buttons.delete")}
</button>
`;
return $(button).appendTo(
this.element
.closest(".dataTables_wrapper")
.find(".dataTables_length")
);
}
deleteRows() {
let checked = this.element.find(".select-row:checked");
if (checked.length === 0) {
return;
}
let confirmationModal = $("#confirmation-modal");
let deleted = [];
confirmationModal
.modal("show")
.find("form")
.on("submit", (e) => {
e.preventDefault();
confirmationModal.modal("hide");
let table = this.element.DataTable();
table.processing(true);
let ids = this.constructor.getRowIds(checked);
// Don't make ajax request if an id was previously deleted.
if (
deleted.length !== 0 &&
_.difference(deleted, ids).length === 0
) {
return;
}
$.ajax({
type: "DELETE",
url: this.route("destroy", { ids: ids.join() }),
success: () => {
deleted = _.flatten(deleted.concat(ids));
this.constructor.setSelectedIds(this.selector, []);
this.constructor.reload(this.element);
},
error: (xhr) => {
error(xhr.responseJSON.message);
deleted = _.flatten(deleted.concat(ids));
this.constructor.setSelectedIds(this.selector, []);
this.constructor.reload(this.element);
},
});
});
}
makeRowClickable(row, id) {
let key = this.hasRoute("show") ? "show" : "edit";
let url = this.route(key, { id });
$(row).addClass("clickable-row").data("href", url);
setTimeout(() => {
$(".clickable-row td:not(:first-child)").css("cursor", "pointer");
});
}
onRowClick(handler) {
let row = "tbody tr.clickable-row td";
if (this.element.find(".select-all").length !== 0) {
row += ":not(:first-child)";
}
this.element.on("click", row, handler);
}
redirectToRowPage(e) {
window.open(
$(e.currentTarget).parent().data("href"),
e.ctrlKey ? "_blank" : "_self"
);
}
selectAllRowsEventListener() {
this.element.find(".select-all").on("change", (e) => {
this.element
.find(".select-row")
.prop("checked", e.currentTarget.checked);
});
}
selectRowEventListener() {
this.element.find(".select-row").on("change", (e) => {
if (e.currentTarget.checked) {
this.appendToSelected(e.currentTarget.value);
} else {
this.removeFromSelected(e.currentTarget.value);
}
});
}
appendToSelected(id) {
id = parseInt(id);
if (!FleetCart.dataTable.selected[this.selector].includes(id)) {
FleetCart.dataTable.selected[this.selector].push(id);
}
}
removeFromSelected(id) {
id = parseInt(id);
FleetCart.dataTable.selected[this.selector].remove(id);
}
checkSelectedCheckboxes(selectedIds) {
let rows = this.element.find(".select-row");
let checkableRows = rows.toArray().filter((row) => {
return selectedIds.includes(parseInt(row.value));
});
$(checkableRows).prop("checked", true);
}
route(name, params) {
let router = FleetCart.dataTable.routes[this.selector][name];
if (typeof router === "string") {
router = { name: router, params };
}
router.params = _.merge(params, router.params);
return window.route(router.name, router.params);
}
hasRoute(name) {
return FleetCart.dataTable.routes[this.selector][name] !== undefined;
}
static setRoutes(selector, routes) {
FleetCart.dataTable.routes[selector] = routes;
}
static setSelectedIds(selector, selected) {
FleetCart.dataTable.selected[selector] = selected;
}
static getSelectedIds(selector) {
return FleetCart.dataTable.selected[selector];
}
static reload(selector, callback, resetPaging = false) {
$(selector).DataTable().ajax.reload(callback, resetPaging);
}
static getRowIds(rows) {
return rows.toArray().reduce((ids, row) => {
return ids.concat(row.value);
}, []);
}
static removeLengthFields() {
$(".dataTables_length select").remove();
}
addErrorHandler() {
$.fn.dataTable.ext.errMode = (settings, helpPage, message) => {
this.element.html(message);
};
}
// https://datatables.net/plug-ins/api/processing()
registerTableProcessingPlugin() {
$.fn.dataTable.Api.register("processing()", function (show) {
return this.iterator("table", function (ctx) {
ctx.oApi._fnProcessingDisplay(ctx, show);
});
});
}
}

View File

@@ -0,0 +1,20 @@
export default class {
appendHiddenInput(form, name, value) {
$('<input>').attr({
type: 'hidden',
name: name ,
value: value,
}).appendTo(form);
}
appendHiddenInputs(form, name, values) {
for (let value of values) {
this.appendHiddenInput(form, name + '[]', value);
}
}
removeErrors() {
$('.has-error > .help-block').remove();
$('.has-error').removeClass('has-error');
}
}

View File

@@ -0,0 +1,215 @@
$.FleetCart = {};
/* ----------------------------------
- FleetCart Options -
---------------------------------- */
$.FleetCart.options = {
animationSpeed: 300,
// Sidebar push menu toggle button selector
sidebarToggleSelector: '[data-toggle=\'offcanvas\']',
// Activate sidebar push menu
sidebarPushMenu: true,
// BoxRefresh Plugin
enableBoxRefresh: true,
// Bootstrap.js tooltip
enableBSToppltip: true,
BSTooltipSelector: '[data-toggle=\'tooltip\']',
// Control Sidebar Tree views
enableControlTreeView: true,
// The standard screen sizes that bootstrap uses.
screenSizes: {
xs: 480,
sm: 768,
md: 992,
lg: 1200,
},
};
/* ----------------------------------
- Implementation -
---------------------------------- */
$(function () {
// Easy access to options
var o = $.FleetCart.options;
// Set up the object
_init();
// Activate layout
$.FleetCart.layout.activate();
// Enable sidebar tree view controls
if (o.enableControlTreeView) {
$.FleetCart.tree('.sidebar');
}
// Activate sidebar push menu
if (o.sidebarPushMenu) {
$.FleetCart.pushMenu.activate(o.sidebarToggleSelector);
}
// Activate Bootstrap tooltip
if (o.enableBSToppltip) {
$('body').tooltip({
selector: o.BSTooltipSelector,
container: 'body',
});
}
});
/* ----------------------------------
- Initialize the FleetCart Object -
---------------------------------- */
function _init() {
// Layout
$.FleetCart.layout = {
activate: function () {
var _this = this;
_this.fix();
$(window, '.wrapper').resize(function () {
_this.fix();
});
},
fix: function () {
var window_height = $(window).height();
$('.wrapper').css('min-height', window_height + 'px');
}
};
// PushMenu
$.FleetCart.pushMenu = {
activate: function (toggleBtn) {
var screenSizes = $.FleetCart.options.screenSizes;
$(document).on('click', toggleBtn, function (e) {
e.preventDefault();
if ($(window).outerWidth() > (screenSizes.md - 1)) {
if ($('body').hasClass('sidebar-collapse')) {
$('body').removeClass('sidebar-collapse').trigger('expanded.pushMenu');
return;
}
$('body').addClass('sidebar-collapse').trigger('collapsed.pushMenu');
return;
}
if ($('body').hasClass('sidebar-open')) {
$('body').removeClass('sidebar-open').removeClass('sidebar-collapse').trigger('collapsed.pushMenu');
return;
}
$('body').addClass('sidebar-open').trigger('expanded.pushMenu');
});
$(window).on('resize', function () {
if ($(window).outerWidth() > (screenSizes.md - 1)) {
return;
} else {
$('body').removeClass('sidebar-collapse');
}
});
$('.content-wrapper').click(function () {
if ($(window).width() <= (screenSizes.md - 1) && $('body').hasClass('sidebar-open')) {
$('body').removeClass('sidebar-open');
}
});
}
};
// Tree
$.FleetCart.tree = function (menu) {
var animationSpeed = $.FleetCart.options.animationSpeed;
$(document).off('click', menu + ' li a')
.on('click', menu + ' li a', function (e) {
var self = $(this);
var checkElement = self.next();
var activeElement = self.closest('.sidebar-menu').find('.active');
if (checkElement.is('.treeview-menu')) {
self.closest('.sidebar-menu').find('.selected').removeClass('selected');
e.preventDefault();
}
if (self.parent().is('.active')) {
activeElement.toggleClass('closed');
} else {
activeElement.addClass('closed');
}
if ((checkElement.is('.treeview-menu')) && (checkElement.is(':visible')) && (!$('body').hasClass('sidebar-collapse'))) {
self.parent().removeClass('selected');
checkElement.slideUp(animationSpeed);
}
else if ((checkElement.is('.treeview-menu')) && (!checkElement.is(':visible'))) {
var ul = self.parents('ul').first().find('ul:visible').slideUp(animationSpeed);
self.parent().addClass('selected');
checkElement.slideDown(animationSpeed);
}
});
};
}
/* ----------------------------------
- Box Refresh Button -
---------------------------------- */
(function ($) {
$.fn.boxRefresh = function (options) {
var settings = $.extend({
trigger: '.refresh-btn',
source: '',
onLoadStart: function (box) {
return box;
},
onLoadDone: function (box) {
return box;
},
}, options);
var overlay = $('<div class="overlay"><div class="fa fa-refresh fa-spin"></div></div>');
return this.each(function () {
if (settings.source === '') {
if (window.console) {
window.console.log('Please specify a source first - boxRefresh()');
}
return;
}
var box = $(this);
var rBtn = box.find(settings.trigger).first();
rBtn.on('click', function (e) {
e.preventDefault();
start(box);
box.find('.box-body').load(settings.source, function () {
done(box);
});
});
});
function start(box) {
box.append(overlay);
settings.onLoadStart.call(box);
}
function done(box) {
box.find(overlay).remove();
settings.onLoadDone.call(box);
}
};
})(jQuery);

View File

@@ -0,0 +1,64 @@
import Chart from 'chart.js';
$(function () {
$.ajax({
type: 'GET',
url: route('admin.sales_analytics.index'),
success(response) {
let data = { labels: response.labels, sales: [], formatted: [], totalOrders: [] };
for (let item of response.data) {
data.sales.push(item.total.amount);
data.formatted.push(item.total.formatted);
data.totalOrders.push(item.total_orders);
}
initSalesAnalyticsChart(data);
},
});
});
function initSalesAnalyticsChart(data) {
new Chart($('.sales-analytics .chart'), {
type: 'bar',
data: {
labels: data.labels,
datasets: [{
data: data.sales,
backgroundColor: [
'rgba(255, 99, 132, 0.5)',
'rgba(54, 162, 235, 0.5)',
'rgba(255, 206, 86, 0.5)',
'rgba(75, 192, 192, 0.5)',
'rgba(153, 102, 255, 0.5)',
'rgba(255, 159, 64, 0.5)',
],
}],
},
barThickness: 1,
options: {
maintainAspectRatio: false,
legend: {
display: false,
},
tooltips: {
displayColors: false,
callbacks: {
label(item) {
let orders = `${trans('admin::dashboard.sales_analytics.orders')}: ${data.totalOrders[item.index]}`;
let sales = `${trans('admin::dashboard.sales_analytics.sales')}: ${data.formatted[item.index]}`;
return [orders, sales];
},
},
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
},
}],
},
},
});
}

View File

@@ -0,0 +1,77 @@
import { ohSnap } from './ohsnap';
export function trans(langKey, replace = {}) {
let line = window.FleetCart.langs[langKey];
for (let key in replace) {
line = line.replace(`:${key}`, replace[key]);
}
return line;
}
export function keypressAction(actions) {
$(document).keypressAction({ actions });
}
export function notify(type, message, { duration = 5000, context = document }) {
let types = {
'info': 'blue',
'success': 'green',
'warning': 'yellow',
'error': 'red',
};
ohSnap(message, {
'container-id': 'notification-toast',
context,
color: types[type],
duration,
});
}
export function info(message, duration) {
notify('info', message, { duration });
}
export function success(message, duration) {
notify('success', message, { duration });
}
export function warning(message, duration) {
notify('warning', message, { duration });
}
export function error(message, duration) {
notify('error', message, { duration });
}
/**
* @see https://stackoverflow.com/a/3955096
*/
if (! Array.prototype.remove) {
Array.prototype.remove = function () {
let what, a = arguments, L = a.length, ax;
while (L && this.length) {
what = a[--L];
while ((ax = this.indexOf(what)) !== -1) {
this.splice(ax, 1);
}
}
return this;
};
}
/**
* @see https://stackoverflow.com/a/4673436
*/
if (! String.prototype.format) {
String.prototype.format = function () {
return this.replace(/%(\d+)%/g, (match, number) => {
return typeof arguments[number] !== 'undefined' ? arguments[number] : match;
});
};
}

View File

@@ -0,0 +1,38 @@
(function ($, window, document, undefined) {
let pluginName = 'keypressAction', defaults = {};
// The actual plugin constructor
function keypressAction(element, options) {
this.element = element;
this.settings = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
this.init();
}
$.extend(keypressAction.prototype, {
bindKeyToRoute(key, route) {
Mousetrap.bind([key], (e) => {
window.location = route;
return false;
});
},
init() {
$.each(this.settings.actions, (index, object) => {
this.bindKeyToRoute(object.key, object.route);
});
},
});
$.fn[pluginName] = function (options) {
this.each(function () {
if (! $.data(this, `plugin_${pluginName}`)) {
$.data(this, `plugin_${pluginName}`, new keypressAction(this, options));
}
});
// chain jQuery functions
return this;
};
})(jQuery, window, document);

View File

@@ -0,0 +1,43 @@
window._ = require('lodash');
window.Sortable = require('sortablejs');
window.$ = window.jQuery = require('jquery');
require('bootstrap');
require('selectize');
require('flatpickr');
require('jquery-slimscroll');
require('mousetrap');
require('datatables.net');
require('datatables.net-bs');
require('./app');
require('./wysiwyg');
require('./jquery.keypressAction');
import Admin from './Admin';
import Form from './Form';
import DataTable from './DataTable';
import { trans, keypressAction, notify, info, success, warning, error } from './functions';
window.admin = new Admin();
window.form = new Form();
window.DataTable = DataTable;
window.trans = trans;
window.keypressAction = keypressAction;
window.notify = notify;
window.info = info;
window.success = success;
window.warning = warning;
window.error = error;
$.ajaxSetup({
headers: {
'Authorization': FleetCart.apiToken,
'X-CSRF-TOKEN': FleetCart.csrfToken,
},
});
$(document).on('preInit.dt', () => {
$('.dataTables_length select').addClass('custom-select-black');
});

View File

@@ -0,0 +1,85 @@
/**
* == OhSnap!.js ==
* A simple jQuery/Zepto notification library designed to be used in mobile apps
*
* author: Justin Domingue
* date: september 18, 2015
* version: 1.0.0
* copyright - nice copyright over here
*/
/* Shows a toast on the page
* Params:
* text: text to show
* options: object that can override the following options
* color: alert will have class 'ohsnap-alert-color'. Default null
* icon: class of the icon to show before the alert. Default null
* duration: duration of the notification in ms. Default 5000ms
* container-id: id of the alert container. Default 'ohsnap'
* fade-duration: duration of the fade in/out of the alerts. Default 'fast'
*/
export function ohSnap(text, options) {
let defaultOptions = {
'color' : null, // color is CSS class `ohsnap-alert-color`
'icon' : null, // class of the icon to show before the alert text
'duration' : 5000, // duration of the notification in ms
'container-id': 'ohsnap', // id of the alert container
'context': document,
'fade-duration': 'fast', // duration of the fade in/out of the alerts. fast, slow or integer in ms
};
options = (typeof options === 'object') ? $.extend(defaultOptions, options) : defaultOptions;
let container = $('#' + options['container-id'], options.context),
icon_markup = '',
color_markup = '';
if (options.icon) {
icon_markup = '<span class=\'' + options.icon + '\'></span> ';
}
if (options.color) {
color_markup = 'ohsnap-alert-' + options.color;
}
// Generate the HTML
let html = $('<div class="ohsnap-alert ' + color_markup + '">' + icon_markup + text + '</div>').fadeIn(options['fade-duration']);
// Append the label to the container
container.append(html);
// Remove the notification on click
html.on('click', function () {
ohSnapX($(this));
});
// After 'duration' seconds, the animation fades out
setTimeout(function () {
ohSnapX(html);
}, options.duration);
}
/* Removes a toast from the page
* params:
* Called without arguments, the function removes all alerts
* element: a jQuery object to remove
* options:
* duration: duration of the alert fade out - 'fast', 'slow' or time in ms. Default 'fast'
*/
export function ohSnapX(element, options) {
let defaultOptions = {
'duration': 'fast',
};
options = (typeof options === 'object') ? $.extend(defaultOptions, options) : defaultOptions;
if (typeof element !== 'undefined') {
element.fadeOut(options.duration, function () {
$(this).remove();
});
} else {
$('.ohsnap-alert').fadeOut(options.duration, function () {
$(this).remove();
});
}
}

View File

@@ -0,0 +1,38 @@
import tinyMCE from 'tinymce';
tinyMCE.baseURL = `${FleetCart.baseUrl}/modules/admin/js/wysiwyg`;
tinyMCE.init({
selector: '.wysiwyg',
theme: 'silver',
mobile: { theme: 'mobile' },
height: 350,
menubar: false,
branding: false,
image_advtab: true,
automatic_uploads: true,
media_alt_source: false,
media_poster: false,
relative_urls: false,
directionality: FleetCart.rtl ? 'rtl' : 'ltr',
cache_suffix: `?v=${FleetCart.version}`,
plugins: 'lists, link, table, image, media, paste, autosave, autolink, wordcount, code, fullscreen',
toolbar: 'styleselect bold italic underline | bullist numlist | alignleft aligncenter alignright | outdent indent | image media link table | code fullscreen',
images_upload_handler(blobInfo, success, failure) {
let formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
$.ajax({
method: 'POST',
url: route('admin.media.store'),
data: formData,
processData: false,
contentType: false,
}).then((file) => {
success(file.path);
}).catch((xhr) => {
failure(xhr.responseJSON.message);
});
},
});

View File

@@ -0,0 +1,257 @@
.accordion-content {
background: #ffffff;
padding: 20px 0px;
border-radius: 3px;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.1);
}
.accordion-box {
> .panel-group {
border-radius: 3px;
overflow: hidden;
}
.panel {
box-shadow: none;
border-bottom: none;
border-radius: 0;
border-color: #e9e9e9;
+ .panel {
margin: 0;
}
}
> .panel-group > .panel:last-child {
border-bottom: 1px solid #e9e9e9;
}
.panel-group {
margin: 0;
}
}
#sliders-accordion > .panel {
border-radius: 3px;
}
.accordion-box .panel-heading {
padding: 0;
background: #f6f6f7;
[data-toggle="collapse"] {
&.collapsed {
background: #ffffff;
transition: all 200ms ease-in-out;
&:hover {
background: #f4f4f4;
}
}
&:after {
position: absolute;
font-family: FontAwesome;
content: "\f107";
right: 10px;
top: 12px;
font-size: 20px;
line-height: 25px;
color: #000000;
transform: rotateX(180deg);
transition: all 200ms ease-in-out;
}
}
}
.accordion-box-content .panel-heading [data-toggle="collapse"]:after {
position: absolute;
font-family: FontAwesome;
content: "\f107";
right: 10px;
top: 12px;
font-size: 20px;
line-height: 25px;
color: #000000;
transform: rotateX(180deg);
transition: all 200ms ease-in-out;
top: 15px;
}
.accordion-box .panel-heading [data-toggle="collapse"].collapsed:after,
.accordion-box-content .panel-heading [data-toggle="collapse"].collapsed:after {
color: #737881;
transform: rotateX(0deg);
}
.accordion-box .panel-heading [data-toggle="collapse"].collapsed:hover:after {
color: #000000;
}
.accordion-box-content {
.panel-heading [data-toggle="collapse"].collapsed:hover:after {
color: #000000;
}
.panel-group .panel + .panel {
margin-top: 0;
border-top: none;
}
}
.accordion-box {
.panel-title a {
position: relative;
padding: 12px 15px;
display: block;
text-decoration: none;
outline: none;
&.has-error.collapsed {
border-left: 3px solid #ff3366;
}
}
.panel-body a {
color: #333333;
display: block;
padding: 14px 15px;
transition: 200ms ease-in-out;
&:hover {
background: #e9e9e9;
}
&.active {
background: #ffffff;
border-top: 1px solid #d2d6de;
border-bottom: 1px solid #d2d6de;
border-left: 3px solid #6f8dfd;
margin-right: -1px;
}
}
.panel-title a {
&:active,
&:hover,
&:focus {
color: #333333;
}
}
.panel-body {
padding: 10px 0 10px 8px;
background: #eeeeee;
}
}
.accordion-box-content {
.tab-content > .form-group:last-child {
margin-bottom: 0;
clear: both;
}
.box-footer {
padding-left: 0;
}
.tab-content-title {
margin-top: 0;
margin-bottom: 20px;
padding-bottom: 8px;
border-bottom: 1px solid #d2d6de;
}
.box-content {
margin-top: 10px;
h4.section-title {
font-weight: 500;
margin-bottom: 10px;
}
}
}
.accordion-tab {
border-bottom: none;
> li {
float: none;
z-index: 0;
> a {
position: relative;
color: #333333;
border-radius: 3px 0 0 3px;
margin-right: -1px;
padding: 14px 15px;
outline: none;
transition: 100ms ease-in-out;
&:hover {
border-color: #e9e9e9;
}
}
&.has-error > a {
border-left: 3px solid #ff3366;
}
&.active > a {
border-left: 3px solid #0068e1;
border-right: 0;
border-right-color: transparent;
border-bottom-color: #e9e9e9;
&:hover,
&:focus {
border-left: 3px solid #0068e1;
border-bottom-color: #e9e9e9;
border-right: 0;
}
}
}
&.nav-tabs > li.active > a {
border-top-color: #e9e9e9;
}
}
.nav-tabs > li.active > a:hover,
.accordion-tab.nav-tabs > li.active > a:focus {
border-top-color: #e9e9e9;
}
.content-accordion {
&.panel-group {
margin-bottom: 15px;
}
.panel {
border-radius: 3px;
border: none;
border: 1px solid #e9e9e9;
}
.panel-heading {
background: #f6f6f7;
padding: 0;
border-radius: 0;
}
.panel-title a {
display: block;
padding: 15px;
&:active,
&:hover,
&:focus {
color: #333333;
}
}
.panel-default > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #e9e9e9;
}
}
@media screen and (max-width: 991px) {
.accordion-box {
margin-bottom: 30px;
}
}

View File

@@ -0,0 +1,68 @@
.alert {
border: none;
color: #555555;
font-size: 15px;
padding: 12px 15px;
border-radius: 3px;
.close {
top: 4px;
right: 0;
outline: 0;
opacity: 0.5;
color: #626060;
text-shadow: none;
font-weight: normal;
transition: 200ms ease-in-out;
&:hover {
opacity: 0.9;
}
}
.alert-text {
display: block;
margin: 6px 20px 0 45px;
}
}
.alert-icon {
float: left;
width: 30px;
height: 30px;
display: table;
border-radius: 50%;
text-align: center;
> i {
font-size: 18px;
display: table-cell;
vertical-align: middle;
}
}
.alert-success {
background: #deedee;
border-left: 3px solid #37bc9b;
.alert-icon {
background: #c5e6e2;
> i {
color: #37bc9b;
}
}
}
.alert-danger {
background: #f2e8ee;
border-left: 3px solid #ff3366;
.alert-icon {
background: #f4ced5;
> i {
color: #ff3366;
}
}
}

View File

@@ -0,0 +1,619 @@
/* buttons */
.btn {
font-size: 15px;
border: none;
border-radius: 3px;
padding: 10px 20px;
transition: 200ms ease-in-out;
}
.btn-default {
background: #f1f1f1;
outline: 0;
border-color: #dddddd;
&.focus,
&:focus,
&.active {
background: #f1f1f1;
outline: 0;
border-color: #dddddd;
}
&:active {
background: #f1f1f1;
outline: 0;
border-color: #dddddd;
&:hover {
background: #f1f1f1;
outline: 0;
border-color: #dddddd;
}
}
&.active:hover,
&:active:focus,
&.active:focus,
&:active.focus,
&.active.focus {
background: #f1f1f1;
outline: 0;
border-color: #dddddd;
}
&.disabled {
&:hover,
&.focus,
&:focus {
background: #f1f1f1;
outline: 0;
border-color: #dddddd;
}
}
&[disabled] {
&:focus,
&:hover,
&.focus,
&.active,
&:active {
background: #f1f1f1;
outline: 0;
border-color: #dddddd;
}
}
}
fieldset[disabled] .btn-default {
&:focus,
&.focus,
&:hover {
background: #f1f1f1;
outline: 0;
border-color: #dddddd;
}
}
.open > .dropdown-toggle.btn-default {
background: #f1f1f1;
outline: 0;
border-color: #dddddd;
&:focus,
&.focus,
&:hover {
background: #f1f1f1;
outline: 0;
border-color: #dddddd;
}
}
.btn-default:hover {
border-color: #dddddd;
background: #e7e7e7;
}
.btn-primary {
background: #0068e1;
outline: 0;
&.focus,
&:focus,
&:active,
&.active,
&:active:hover,
&.active:hover,
&:active:focus,
&.active:focus,
&:active.focus,
&.active.focus {
background: #0068e1;
outline: 0;
}
&.disabled {
&:hover,
&.focus,
&:focus {
background: #0068e1;
outline: 0;
}
}
&[disabled] {
&:focus,
&:hover,
&.focus,
&.active,
&:active {
background: #0068e1;
outline: 0;
}
}
}
fieldset[disabled] .btn-primary {
&:focus,
&.focus,
&:hover {
background: #0068e1;
outline: 0;
}
}
.open > .dropdown-toggle.btn-primary {
background: #0068e1;
outline: 0;
&:focus,
&.focus,
&:hover {
background: #0068e1;
outline: 0;
}
}
.btn-primary:hover {
background: #0059bd;
}
.btn-danger {
background: #fc4b4b;
&.focus,
&:focus,
&.active {
background: #fc4b4b;
}
&:active {
background: #fc4b4b;
&:hover {
background: #fc4b4b;
}
}
&.active:hover,
&:active:focus,
&.active:focus,
&:active.focus,
&.active.focus {
background: #fc4b4b;
}
&.disabled {
&:hover,
&.focus,
&:focus {
background: #fc4b4b;
}
}
&[disabled] {
&:focus,
&:hover,
&.focus,
&.active,
&:active {
background: #fc4b4b;
}
}
}
fieldset[disabled] .btn-danger {
&:focus,
&.focus,
&:hover {
background: #fc4b4b;
}
}
.open > .dropdown-toggle.btn-danger {
background: #fc4b4b;
&:focus,
&.focus,
&:hover {
background: #fc4b4b;
}
}
.btn-danger:hover {
background: #ff7070;
}
.btn-info {
background: #4bcffc;
&.focus,
&:focus,
&.active {
background: #4bcffc;
}
&:active {
background: #4bcffc;
&:hover {
background: #4bcffc;
}
}
&.active:hover,
&:active:focus,
&.active:focus,
&:active.focus,
&.active.focus {
background: #4bcffc;
}
&.disabled {
&:hover,
&.focus,
&:focus {
background: #4bcffc;
}
}
&[disabled] {
&:focus,
&:hover,
&.focus,
&.active,
&:active {
background: #4bcffc;
}
}
}
fieldset[disabled] .btn-info {
&:focus,
&.focus,
&:hover {
background: #4bcffc;
}
}
.open > .dropdown-toggle.btn-info {
background: #4bcffc;
&:focus,
&.focus,
&:hover {
background: #4bcffc;
}
}
.btn-info:hover {
background: #6fcffd;
}
.btn {
&:focus,
&:hover:focus,
&:active:focus {
outline: 0;
box-shadow: none;
}
}
.btn-loading {
position: relative;
color: transparent !important;
&:after {
position: absolute;
content: "";
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
height: 16px;
width: 16px;
border: 2px solid #ffffff;
border-radius: 100%;
border-right-color: transparent;
border-top-color: transparent;
animation: spinAround 600ms infinite linear;
}
&.btn-default:after {
border: 2px solid #0068e1;
border-right-color: transparent;
border-top-color: transparent;
}
}
@keyframes spinAround {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
/* bg color */
.bg-black {
background: #494f5a;
}
.bg-red {
background: #fc4b4b;
}
.bg-green {
background: #37bc9b;
}
.bg-blue {
background: #0068e1;
}
/* text */
.text-black {
color: #494f5a;
}
.text-red {
color: #fc4b4b;
&:hover,
&:focus {
color: #fc4b4b;
}
}
.text-green {
color: #37bc9b;
}
.text-blue {
color: #0068e1;
}
/* label */
.label {
font-weight: normal;
padding: 5px 10px;
}
.label-default {
background: #d2d6de;
}
.label-primary {
background: #0068e1;
}
.label-success {
background: #37bc9b;
}
.label-danger {
background: #fc4b4b;
}
/* form error */
.has-error {
.help-block,
.control-label,
.radio,
.checkbox,
.radio-inline,
.checkbox-inline,
&.radio label,
&.checkbox label,
&.radio-inline label,
&.checkbox-inline label {
color: #ff3366;
}
.form-control {
border-color: #ff3366;
box-shadow: none;
&:focus {
border-color: #ff3366;
box-shadow: none;
}
}
.input-group-addon {
color: #ff3366;
background-color: #f2dede;
border-color: #ff3366;
}
.form-control-feedback {
color: #ff3366;
}
}
.help-block {
margin-bottom: 0;
}
.checkbox {
label {
font-size: 15px;
color: #333333;
margin-bottom: 0 !important;
}
[type="checkbox"] {
&:checked,
&:not(:checked) {
position: absolute;
display: none;
}
&:checked + label,
&:not(:checked) + label {
font-family: "Roboto", sans-serif;
position: relative;
padding-left: 28px;
cursor: pointer;
display: inline-block;
text-align: left;
}
&:checked + label:before,
&:not(:checked) + label:before {
content: "";
position: absolute;
left: 0;
top: 1px;
width: 17px;
height: 17px;
border-radius: 3px;
background: #e9e9e9;
transition: 200ms ease-in-out;
}
&:checked + label:after,
&:not(:checked) + label:after {
content: "\f00c";
font-family: FontAwesome;
font-size: 13px;
position: absolute;
top: 1px;
left: 2px;
color: #ffffff;
-webkit-text-stroke: 1px #0068e1;
transition: 200ms ease-in-out;
}
&:checked + label:before {
background: #0068e1;
}
&:not(:checked) + label:after {
opacity: 0;
transform: scale(0);
}
&:checked + label:after {
-webkit-text-stroke: 1px #0068e1;
opacity: 1;
transform: scale(1);
}
}
}
/* radio button */
.radio {
text-align: left;
label {
color: #333333;
}
[type="radio"] {
&:checked,
&:not(:checked) {
position: absolute;
left: -9999px;
}
&:checked + label,
&:not(:checked) + label {
font-family: "Roboto", sans-serif;
position: relative;
padding-left: 28px;
cursor: pointer;
line-height: 22px;
display: inline-block;
}
&:checked + label:before,
&:not(:checked) + label:before {
content: "";
position: absolute;
left: 0;
top: 1px;
width: 19px;
height: 19px;
border: 1px solid #d2d6de;
border-radius: 100%;
background: #ffffff;
}
&:checked + label:after {
content: "";
width: 13px;
height: 13px;
background: #0068e1;
position: absolute;
top: 4px;
left: 3px;
border-radius: 100%;
transition: 200ms ease-in-out;
}
&:not(:checked) + label:after {
content: "";
width: 14px;
height: 14px;
background: #0068e1;
position: absolute;
top: 3px;
left: 3px;
border-radius: 100%;
transition: 200ms ease-in-out;
opacity: 0;
transform: scale(0);
}
&:checked + label:after {
opacity: 1;
transform: scale(1);
}
}
+ .radio {
margin-top: 0;
}
}
.checkbox + .checkbox {
margin-top: 0;
}
/* select option */
.custom-select-white {
appearance: none;
background: #f9f9f9 url('../images/arrow-white.png') no-repeat right 8px center;
background-size: 10px;
line-height: normal !important;
height: 40px;
padding: 0 30px 0 10px;
border-radius: 3px;
border-color: #d9d9d9;
}
.custom-select-black {
appearance: none;
background: #ffffff url('../images/arrow-black.png') no-repeat right 8px center;
background-size: 10px;
line-height: normal !important;
height: 40px;
padding: 0 30px 0 10px;
border-radius: 3px;
border-color: #d9d9d9;
}
.custom-select-white:focus,
.custom-select-black:focus {
outline: 0;
}

View File

@@ -0,0 +1,152 @@
.grid {
.single-grid {
position: relative;
overflow: hidden;
height: 130px;
padding: 0 15px;
background: #ffffff;
border-radius: 3px;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.1);
h4 {
margin: 22px 0;
}
span {
font-size: 26px;
margin-top: 3px;
}
i {
font-size: 36px;
position: absolute;
left: 15px;
bottom: 22px;
margin: 0;
}
&.total-orders {
i {
color: rgba(0, 104, 225, 0.7);
}
}
&.total-sales {
i {
color: rgba(112, 124, 210, 0.7);
}
}
&.total-customers {
i {
color: rgba(255, 51, 102, 0.7);
}
}
&.total-products {
i {
color: rgba(55, 188, 155, 0.7);
}
}
}
}
.dashboard-panel {
margin-top: 30px;
padding: 0 15px;
background: #ffffff;
overflow: hidden;
border-radius: 3px;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.1);
.table-responsive {
margin-bottom: 10px;
}
}
.grid-header {
overflow: auto;
h4 {
margin: 15px 0;
float: left;
i {
margin-right: 8px;
}
}
}
.sales-analytics {
background: #ffffff;
margin-top: 30px;
padding: 0 15px 15px;
border-radius: 3px;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.1);
.chart {
height: 282px !important;
}
}
.anchor-table {
.table {
> tbody {
> tr {
> td {
padding: 0;
a {
display: block;
padding: 12px 8px;
color: #626060;
text-decoration: none;
}
&.empty {
text-align: center;
padding: 10px 0;
}
}
&:hover {
a {
color: #0059bd;
}
}
}
}
}
}
.search-terms {
tbody > tr > td {
color: #626060;
padding: 12px 8px;
&.empty {
text-align: center;
padding: 10px 0;
}
}
}
@media screen and (max-width: 1199px) {
.single-grid {
&.total-customers {
margin-top: 15px;
}
&.total-products {
margin-top: 15px;
}
}
}
@media screen and (max-width: 767px) {
.single-grid {
&.total-orders {
margin-top: 15px;
}
}
}

View File

@@ -0,0 +1,220 @@
.index-table {
.label {
padding: 6px 10px;
}
> .loading-spinner {
display: table;
margin: 0 auto;
margin-bottom: 10px;
}
}
.dataTable.table {
border-bottom: 1px solid #e9e9e9;
> {
thead > tr > th,
tfoot > tr > th {
font-family: 'Open Sans', sans-serif;
color: #4a4a4a;
padding: 10px 15px;
border-color: #e9e9e9;
&:after {
padding: 5px;
}
}
tbody > tr {
transition: 200ms ease-in-out;
&:first-child > td {
border: none;
}
&:nth-of-type(2n+1) {
background: #ffffff;
&:hover {
background: #f9f9f9;
}
}
> td {
padding: 15px;
vertical-align: middle;
outline: 0;
}
}
}
.checkbox {
margin-bottom: 0;
}
.thumbnail-holder {
position: relative;
border: 1px solid #d9d9d9;
background: #ffffff;
height: 50px;
width: 50px;
border-radius: 3px;
overflow: hidden !important;
> {
i {
position: absolute;
font-size: 24px;
color: #d9d9d9;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
img {
position: absolute;
left: 50%;
top: 50%;
max-height: 100%;
max-width: 100%;
transform: translate(-50%, -50%);
}
}
}
}
table.dataTable {
margin: 5px 0 !important;
thead {
.sorting,
.sorting_asc,
.sorting_desc,
.sorting_asc_disabled,
.sorting_desc_disabled {
&:after {
bottom: 3px;
opacity: 0.5;
}
}
}
}
div.dataTables_length {
display: flex;
> label {
margin-bottom: 0;
}
select.input-sm {
margin: 0 15px 0 5px;
}
}
div.dataTables_wrapper {
margin-top: 5px;
&.dataTables_filter input {
margin-left: 10px;
}
div.dataTables_processing {
position: absolute;
top: 0;
left: 0;
margin: 0;
width: 100%;
height: 100%;
border: 0;
text-align: center;
background: rgba(255, 255, 255, 0.7);
z-index: 999;
.fa-spin {
position: absolute;
top: 50%;
left: 50%;
color: #000;
font-size: 30px;
}
}
.dataTables_paginate {
margin-top: 8px !important;
ul.pagination {
margin: 0;
}
}
}
.btn-delete {
margin-left: 10px;
}
.dataTables_empty {
color: #626060;
}
.dataTable.translations-table {
margin: 5px -15px !important;
padding: 0 15px;
> tbody > tr > td {
min-width: 150px;
}
}
@media screen and (max-width: 991px) {
#products-table {
.dataTable.table {
> tbody > tr > td:nth-child(3) {
min-width: 200px;
}
}
}
}
@media screen and (max-width: 767px) {
div.table-responsive {
> div.dataTables_wrapper > div.row > div[class^="col-"] {
&:first-child {
padding-right: 0;
}
&:last-child {
padding-left: 0;
}
}
}
div.dataTables_wrapper {
> .row > .col-sm-6 {
width: 100%;
}
div.dataTables_filter {
text-align: left;
margin-top: 15px;
}
}
}
@media screen and (max-width: 340px) {
div.dataTables_length {
display: block;
.btn-delete {
display: table;
margin: 15px 0 0 0;
}
}
div.dataTables_wrapper {
div.dataTables_length {
text-align: left;
}
}
}

View File

@@ -0,0 +1,43 @@
@import '~flatpickr/dist/flatpickr';
.flatpickr-calendar {
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.15);
}
.rtl {
.flatpickr-calendar {
&:before,
&:after {
right: 22px;
left: auto;
}
}
}
.flatpickr-month {
height: 50px;
}
.flatpickr-prev-month,
.flatpickr-next-month {
top: 10px;
}
.rtl {
.flatpickr-prev-month,
.flatpickr-next-month {
transform: rotateY(180deg);
}
}
span.flatpickr-current-month {
.cur-month {
font-family: "Open Sans", sans-serif !important;
}
}
.flatpickr-weekdays {
span {
font-family: "Open Sans", sans-serif;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,195 @@
@import '~bootstrap/dist/css/bootstrap';
@import '~font-awesome/css/font-awesome';
@import '~nprogress/nprogress';
@import '~datatables.net-bs/css/dataTables.bootstrap';
@import '~flatpickr/dist/ie';
@import './datatables';
@import './selectize';
@import './wysiwyg';
@import './ohsnap';
@import './flatpickr';
@import './classes';
@import './tab';
@import './alert';
@import './modal';
@import './utilities';
@import './accordion';
@import './fleetcart';
@import './panel';
html {
direction: ltr;
}
.btn-actions {
margin: 0 15px 15px 0;
> i {
margin-right: 5px;
}
}
.overflow-hidden {
overflow: hidden;
}
#nprogress {
.bar {
background: #0068e1;
}
.peg {
box-shadow: 0 0 10px #0068e1, 0 0 5px #0068e1;
}
.spinner-icon {
border-top-color: #0068e1;
border-left-color: #0068e1;
}
}
.sortable-ghost {
opacity: .2;
}
.btn-group.open .dropdown-toggle,
.btn-group .dropdown-toggle:active {
-webkit-box-shadow: none;
box-shadow: none;
}
.dot {
height: 10px;
width: 10px;
border-radius: 50%;
display: inline-block;
}
.green {
background-color: #37bc9b;
}
.red {
background-color: #fc4b4b;
}
.options {
tr td:first-child {
width: 34px;
min-width: 34px;
}
tr td:last-child {
width: 60px;
}
.drag-icon {
font-size: 16px;
color: #737881;
cursor: move;
vertical-align: top;
margin-top: 12px;
white-space: nowrap;
display: inline-block;
i {
float: left;
&:nth-child(2) {
margin-left: 1px;
}
}
}
.choose-file,
.delete-row {
padding: 10px 15px;
}
}
.pagination {
margin-bottom: 10px;
> li {
float: left;
margin-left: -1px;
padding: 0;
border: 1px solid #e9e9e9;
transition: 200ms ease-in-out;
&:hover {
cursor: pointer;
background: #f1f1f1;
}
&:first-child {
margin-left: 0;
border-radius: 3px 0 0 3px;
a {
border-radius: 3px 0 0 3px;
}
}
&:last-child {
border-radius: 0 3px 3px 0;
a {
border-radius: 0 3px 3px 0;
}
}
&.disabled {
cursor: not-allowed !important;
&:hover {
background: transparent;
}
}
> a {
padding: 10px 15px;
color: #0068e1;
border: none;
display: block;
background: transparent !important;
text-decoration: none;
&:hover {
background: transparent;
}
&:hover, &:focus {
color: #0068e1;
}
}
> span {
display: block;
padding: 10px 15px;
border: none;
}
}
> .active {
background: #0068e1;
cursor: default !important;
border-color: #0068e1;
&:hover {
background: #0068e1;
}
> a {
background: transparent !important;
}
> span {
background: transparent !important;
}
}
}
.empty {
color: #626060;
}

View File

@@ -0,0 +1,75 @@
.modal {
background: rgba(0, 0, 0, 0.3);
&.fade {
.modal-dialog {
opacity: 0;
transform: scale(0.9);
transition: 100ms ease-in-out;
}
&.in .modal-dialog {
transform: scale(1);
opacity: 1;
}
}
.modal-header {
padding: 15px;
border-bottom-color: #e9e9e9;
> .close {
font-size: 18px;
outline: 0;
margin-top: 2px;
-webkit-text-stroke: 1px #ffffff;
transition: 200ms ease-in-out;
}
}
.modal-content {
border: none;
border-radius: 3px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.12);
}
.modal-footer {
border-radius: 0 0 3px 3px;
border-top-color: #e9e9e9;
.btn + .btn {
margin-left: 12px;
}
}
}
#confirmation-modal {
.modal-header {
padding: 15px 15px 15px 25px;
}
.modal-body {
padding: 0px 15px 25px 25px;
}
.modal-header,
.modal-footer {
border: none;
}
.default-message {
color: #737881;
}
.modal-footer {
background: #f1f3f7;
}
.btn-default {
background: #e1e1e1;
&:hover {
background: #e9e9e9;
}
}
}

View File

@@ -0,0 +1,41 @@
#notification-toast {
position: fixed;
bottom: 0;
right: 15px;
z-index: 999;
}
.ohsnap-alert {
padding: 10px 15px;
margin-bottom: 15px;
border-radius: 3px;
float: right;
clear: right;
background-color: white;
z-index: 999;
}
.ohsnap-alert-red {
color: #ffffff;
background-color: #da4453;
}
.ohsnap-alert-green {
color: #ffffff;
background-color: #37bc9b;
}
.ohsnap-alert-blue {
color: #ffffff;
background-color: #4a89dc;
}
.ohsnap-alert-yellow {
color: #ffffff;
background-color: #f6bb42;
}
.ohsnap-alert-orange {
color:#fff;
background-color: #e9573f;
}

View File

@@ -0,0 +1,104 @@
.panel-wrap {
.panel {
margin-bottom: 15px;
box-shadow: none;
border: 1px solid #e9e9e9;
border-radius: 3px;
.panel-header {
padding: 15px;
background: #f6f6f7;
border-bottom: 1px solid #e9e9e9;
.drag-icon {
font-size: 16px;
display: inline-block;
margin: 2px 10px 0 0;
color: #737881;
cursor: move;
white-space: nowrap;
> i {
float: left;
&:last-child {
margin-left: 1px;
}
}
}
.btn {
padding: 0;
background: transparent;
i {
-webkit-text-stroke: 1px #f6f6f7;
}
}
}
.panel-body {
position: relative;
padding: 15px;
}
.panel-image {
position: absolute;
left: 15px;
top: 25px;
display: flex;
height: 110px;
width: 110px;
justify-content: center;
align-items: center;
border: 1px solid #d2d6de;
border-radius: 3px;
cursor: pointer;
> img {
max-height: 100%;
max-width: 100%;
}
> i {
position: absolute;
left: 50%;
top: 50%;
font-size: 48px;
color: #d9d9d9;
transform: translate(-50%, -50%);
}
}
.panel-content {
margin-left: 130px;
padding: 10px 0;
.checkbox {
padding-top: 0;
}
}
.form-group {
margin-left: 0;
margin-right: 0;
}
}
}
@media screen and (max-width: 767px) {
.panel-wrap {
.panel {
.panel-image {
position: relative;
left: auto;
top: auto;
margin: 15px auto 30px;
}
.panel-content {
margin-left: 0;
}
}
}
}

View File

@@ -0,0 +1,114 @@
@import '~selectize/dist/css/selectize';
.selectize-control:not(.multi) {
.selectize-input .item,
.selectize-dropdown .option {
font-size: 14px;
}
.plugin-remove_button [data-value] {
padding-right: 0 !important;
}
}
.selectize-control {
&.multi .selectize-input > div {
padding: 4px 8px;
}
&.single .selectize-input {
cursor: text;
&:after {
content: none;
}
> span {
display: flex;
padding: 2px 0
}
input {
cursor: text;
position: absolute;
left: -10000px;
}
}
&.plugin-remove_button {
[data-value] .remove {
display: flex;
align-items: center;
justify-content: center;
border-left-color: #e9e9e9;
}
.remove-single {
top: 1px;
color: #333333;
text-decoration: none;
}
}
}
.selectize-input {
border-radius: 3px;
border-color: #d9d9d9;
min-height: 40px;
vertical-align: bottom;
box-shadow: none !important;
transition: border-color ease-in-out .15s;
.dropdown-active {
-webkit-border-radius: 0;
border-radius: 0;
}
> input {
margin-top: 2px !important;
}
input {
font-size: 15px;
transition: 0ms !important;
}
input::-moz-placeholder {
color: #999999;
opacity: 1;
}
input:-ms-input-placeholder {
color: #999999;
}
input::-webkit-input-placeholder {
color: #999999;
}
&.focus {
border-color: #6f8dfd;
box-shadow: 0 0 2px rgba(30, 140, 190, .8);
}
.item {
border-radius: 3px;
float: left;
white-space: normal;
word-break: break-all;
}
}
.selectize-dropdown {
[data-selectable] {
cursor: pointer;
}
.active {
background-color: #f9f9f9;
}
.selectize-dropdown-content .create strong {
font-family: "Open Sans", sans-serif;
font-weight: 600;
}
}

View File

@@ -0,0 +1,67 @@
.tab-wrapper > ul {
display: block;
margin-bottom: 20px;
> li {
margin: 0;
&.active > a {
border: none !important;
color: #333333;
&:focus {
border: none !important;
color: #333333;
}
&:hover {
border: none;
}
&:after {
width: 100% !important;
}
}
&.has-error {
> a:after {
width: 100% !important;
}
&:not(.active) > a:after {
background: #ff3366;
}
}
> a {
position: relative;
font-size: 14px;
color: #777777;
border: none;
margin: 0;
&:after {
position: absolute;
content: "";
bottom: -1px;
left: 0;
right: 0;
margin: auto;
width: 0;
background: #0068e1;
height: 1px;
transition: 200ms ease-in-out;
}
}
> a:hover,
&.active > a:hover {
background: transparent;
color: #333333;
}
> a:hover:after {
width: 50%;
}
}
}

View File

@@ -0,0 +1,191 @@
.p-tb-0 {
padding-top: 0;
padding-bottom: 0;
}
.p-tb-5 {
padding-top: 5px;
padding-bottom: 5px;
}
.p-tb-10 {
padding-top: 10px;
padding-bottom: 10px;
}
.p-tb-15 {
padding-top: 15px;
padding-bottom: 15px;
}
.p-t-0 {
padding-top: 0;
}
.p-t-5 {
padding-top: 5px;
}
.p-t-10 {
padding-top: 10px;
}
.p-t-15 {
padding-top: 15px;
}
.p-b-0 {
padding-bottom: 0;
}
.p-b-5 {
padding-bottom: 5px;
}
.p-b-10 {
padding-bottom: 10px;
}
.p-b-15 {
padding-bottom: 15px;
}
.p-l-0 {
padding-left: 0;
}
.p-l-5 {
padding-left: 5px;
}
.p-l-10 {
padding-left: 10px;
}
.p-l-15 {
padding-left: 15px;
}
.p-r-0 {
padding-right: 0;
}
.p-r-5 {
padding-right: 5px;
}
.p-r-10 {
padding-right: 10px;
}
.p-r-15 {
padding-right: 15px;
}
.m-tb-0 {
margin-top: 0;
margin-bottom: 0;
}
.m-tb-5 {
margin-top: 5px;
margin-bottom: 5px;
}
.m-tb-10 {
margin-top: 10px;
margin-bottom: 10px;
}
.m-tb-15 {
margin-top: 15px;
margin-bottom: 15px;
}
.m-t-0 {
margin-top: 0;
}
.m-t-5 {
margin-top: 5px;
}
.m-t-10 {
margin-top: 10px;
}
.m-t-15 {
margin-top: 15px;
}
.m-b-0 {
margin-bottom: 0;
}
.m-b-5 {
margin-bottom: 5px;
}
.m-b-10 {
margin-bottom: 10px;
}
.m-b-15 {
margin-bottom: 15px;
}
.m-l-0 {
margin-left: 0;
}
.m-l-5 {
margin-left: 5px;
}
.m-l-10 {
margin-left: 10px;
}
.m-l-15 {
margin-left: 15px;
}
.m-r-0 {
margin-right: 0;
}
.m-r-5 {
margin-right: 5px;
}
.m-r-10 {
margin-right: 10px;
}
.m-r-15 {
margin-right: 15px;
}
.no-border {
border: 0 !important;
}
.no-padding {
padding: 0 !important;
}
.no-margin {
margin: 0 !important;
}
.no-shadow {
box-shadow: none !important;
}
.cursor-auto {
cursor: auto !important;
}
.text-left {
text-align: left;
}

View File

@@ -0,0 +1,15 @@
.tox {
&.tox-tinymce-aux {
.tox-toolbar__overflow {
min-width: 55px;
}
}
.tox-menu {
&.tox-collection.tox-collection--list {
.tox-collection__group {
min-width: 178px;
}
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
return [
'visit_store' => 'Visit Store',
'form' => [
'please_select' => 'Please Select',
],
'buttons' => [
'save' => 'Save',
'delete' => 'Delete',
'cancel' => 'Cancel',
],
'table' => [
'id' => 'ID',
'status' => 'Status',
'created' => 'Created',
'date' => 'Date',
],
'pagination' => [
'previous' => 'Previous',
'next' => 'Next',
],
'delete' => [
'confirmation' => 'Confirmation',
'confirmation_message' => 'Are you sure you want to delete?',
],
'shortcuts' => [
'available_shortcuts' => 'Available keyboard shortcuts on this page',
'this_menu' => 'This Menu',
'back_to_index' => 'Back to :name Index',
],
'errors' => [
'404' => '404',
'404_title' => 'Oops! This page was not found',
'404_description' => 'The page you are looking for was not found.',
'500' => '500',
'500_title' => 'Oops! Something went wrong',
'500_description' => 'An administrator was notified.',
],
];

View File

@@ -0,0 +1,44 @@
<?php
return [
'dashboard' => 'Dashboard',
'total_sales' => 'Total Sales',
'total_orders' => 'Total Orders',
'total_products' => 'Total Products',
'total_customers' => 'Total Customers',
'no_data' => 'No data available!',
'latest_search_terms' => 'Latest Search Terms',
'latest_orders' => 'Latest Orders',
'latest_reviews' => 'Latest Reviews',
'table' => [
'customer' => 'Customer',
'latest_search_terms' => [
'keyword' => 'Keyword',
'results' => 'Results',
'hits' => 'Hits',
],
'latest_orders' => [
'order_id' => 'Order ID',
'status' => 'Status',
'total' => 'Total',
],
'latest_reviews' => [
'product' => 'Product',
'rating' => 'Rating',
],
],
'sales_analytics_title' => 'Sales Analystics',
'sales_analytics' => [
'orders' => 'Orders',
'sales' => 'Sales',
'day_names' => [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
],
],
];

View File

@@ -0,0 +1,7 @@
<?php
return [
'resource_saved' => ':resource has been saved.',
'resource_deleted' => ':resource has been deleted.',
'permission_denied' => 'Permission Denied (required permission: ":permission").',
];

View File

@@ -0,0 +1,8 @@
<?php
return [
'create' => 'Create :resource',
'show' => 'Show :resource',
'edit' => 'Edit :resource',
'delete' => 'Delete :resource',
];

View File

@@ -0,0 +1,10 @@
<?php
return [
'content' => 'Content',
'sales' => 'Sales',
'system' => 'System',
'localization' => 'Localization',
'appearance' => 'Appearance',
'tools' => 'Tools',
];

View File

@@ -0,0 +1,44 @@
<div class="accordion-content clearfix">
<div class="col-lg-3 col-md-4">
<div class="accordion-box">
<div class="panel-group" id="{{ $name }}">
@foreach ($groups as $group => $options)
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a
@if (count($groups) > 1)
class="{{ ($options['active'] ?? false) ? '' : 'collapsed' }} {{ $tabs->group($group)->hasError() ? 'has-error' : '' }}"
data-toggle="collapse"
data-parent="#{{ $name }}"
href="#{{ $group }}"
@endif
>
{{ $options['title'] }}
</a>
</h4>
</div>
<div id="{{ $group }}" class="panel-collapse collapse {{ ($options['active'] ?? false) ? 'in' : '' }}">
<div class="panel-body">
<ul class="accordion-tab nav nav-tabs">
{{ $tabs->group($group)->navs() }}
</ul>
</div>
</div>
</div>
@endforeach
</div>
</div>
</div>
<div class="col-lg-9 col-md-8">
<div class="accordion-box-content">
<div class="tab-content clearfix">
{{ $contents }}
@include('admin::form.footer')
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,17 @@
@section('title')
@isset($subtitle)
{{ "{$subtitle} - {$title}" }}
@else
{{ $title }}
@endisset
@endsection
@section('content_header')
<h3>{{ $title }}</h3>
<ol class="breadcrumb">
<li><a href="{{ route('admin.dashboard.index') }}">{{ trans('admin::dashboard.dashboard') }}</a></li>
{{ $slot }}
</ol>
@endsection

View File

@@ -0,0 +1,67 @@
@section('content')
<div class="row">
<div class="btn-group pull-right">
@if (isset($buttons, $name))
@foreach ($buttons as $view)
<a href="{{ route("admin.{$resource}.{$view}") }}" class="btn btn-primary btn-actions btn-{{ $view }}">
{{ trans("admin::resource.{$view}", ['resource' => $name]) }}
</a>
@endforeach
@else
{{ $buttons ?? '' }}
@endif
</div>
</div>
<div class="box box-primary">
<div class="box-body index-table" id="{{ isset($resource) ? "{$resource}-table" : '' }}">
@if (isset($thead))
@include('admin::components.table')
@else
{{ $slot }}
@endif
</div>
</div>
@endsection
@isset($name)
@push('shortcuts')
@if (isset($buttons) && in_array('create', $buttons))
<dl class="dl-horizontal">
<dt><code>c</code></dt>
<dd>{{ trans('admin::resource.create', ['resource' => $name]) }}</dd>
</dl>
@endif
<dl class="dl-horizontal">
<dt><code>Del</code></dt>
<dd>{{ trans('admin::resource.delete', ['resource' => $name]) }}</dd>
</dl>
@endpush
@push('scripts')
<script>
@if (isset($buttons) && in_array('create', $buttons))
keypressAction([
{ key: 'c', route: '{{ route("admin.{$resource}.create") }}'}
]);
@endif
Mousetrap.bind('del', function () {
$('.btn-delete').trigger('click');
});
Mousetrap.bind('backspace', function () {
$('.btn-delete').trigger('click');
});
@isset($resource)
DataTable.setRoutes('#{{ $resource }}-table .table', {
index: '{{ "admin.{$resource}.index" }}',
edit: '{{ "admin.{$resource}.edit" }}',
destroy: '{{ "admin.{$resource}.destroy" }}',
});
@endisset
</script>
@endpush
@endisset

View File

@@ -0,0 +1,11 @@
<div class="table-responsive">
<table class="table table-striped table-hover {{ $class ?? '' }}" id="{{ $id ?? '' }}">
<thead>{{ $thead }}</thead>
<tbody>{{ $slot }}</tbody>
@isset($tfoot)
<tfoot>{{ $tfoot }}</tfoot>
@endisset
</table>
</div>

View File

@@ -0,0 +1,8 @@
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="single-grid total-customers">
<h4>{{ trans('admin::dashboard.total_customers') }}</h4>
<i class="fa fa-users pull-left" aria-hidden="true"></i>
<span class="pull-right">{{ $totalCustomers }}</span>
</div>
</div>

View File

@@ -0,0 +1,8 @@
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="single-grid total-orders">
<h4>{{ trans('admin::dashboard.total_orders') }}</h4>
<i class="fa fa-shopping-cart pull-left" aria-hidden="true"></i>
<span class="pull-right">{{ $totalOrders }}</span>
</div>
</div>

View File

@@ -0,0 +1,8 @@
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="single-grid total-products">
<h4>{{ trans('admin::dashboard.total_products') }}</h4>
<i class="fa fa-cubes" aria-hidden="true"></i>
<span class="pull-right">{{ $totalProducts }}</span>
</div>
</div>

View File

@@ -0,0 +1,8 @@
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="single-grid total-sales">
<h4>{{ trans('admin::dashboard.total_sales') }}</h4>
<i class="fa fa-money pull-left" aria-hidden="true"></i>
<span class="pull-right">{{ $totalSales->format() }}</span>
</div>
</div>

View File

@@ -0,0 +1,46 @@
@extends('admin::layout')
@section('title', trans('admin::dashboard.dashboard'))
@section('content_header')
<h2 class="pull-left">{{ trans('admin::dashboard.dashboard') }}</h2>
@endsection
@section('content')
<div class="grid clearfix">
<div class="row">
@hasAccess('admin.orders.index')
@include('admin::dashboard.grids.total_sales')
@include('admin::dashboard.grids.total_orders')
@endHasAccess
@hasAccess('admin.products.index')
@include('admin::dashboard.grids.total_products')
@endHasAccess
@hasAccess('admin.users.index')
@include('admin::dashboard.grids.total_customers')
@endHasAccess
</div>
</div>
<div class="row">
<div class="col-md-7">
@hasAccess('admin.orders.index')
@include('admin::dashboard.panels.sales_analytics')
@endHasAccess
@hasAccess('admin.orders.index')
@include('admin::dashboard.panels.latest_orders')
@endHasAccess
</div>
<div class="col-md-5">
@include('admin::dashboard.panels.latest_search_terms')
@hasAccess('admin.reviews.index')
@include('admin::dashboard.panels.latest_reviews')
@endHasAccess
</div>
</div>
@endsection

View File

@@ -0,0 +1,50 @@
<div class="dashboard-panel">
<div class="grid-header">
<h4><i class="fa fa-shopping-cart" aria-hidden="true"></i>{{ trans('admin::dashboard.latest_orders') }}</h4>
</div>
<div class="clearfix"></div>
<div class="table-responsive anchor-table">
<table class="table">
<thead>
<tr>
<th>{{ trans('admin::dashboard.table.latest_orders.order_id') }}</th>
<th>{{ trans('admin::dashboard.table.customer') }}</th>
<th>{{ trans('admin::dashboard.table.latest_orders.status') }}</th>
<th>{{ trans('admin::dashboard.table.latest_orders.total') }}</th>
</tr>
</thead>
<tbody>
@forelse ($latestOrders as $latestOrder)
<tr>
<td>
<a href="{{ route('admin.orders.show', $latestOrder) }}">
{{ $latestOrder->id }}
</a>
</td>
<td>
<a href="{{ route('admin.orders.show', $latestOrder) }}">
{{ $latestOrder->customer_full_name }}
</a>
</td>
<td>
<a href="{{ route('admin.orders.show', $latestOrder) }}">
{{ $latestOrder->status() }}
</a>
</td>
<td>
<a href="{{ route('admin.orders.show', $latestOrder) }}">
{{ $latestOrder->total->format() }}
</a>
</td>
</tr>
@empty
<tr>
<td class="empty" colspan="5">{{ trans('admin::dashboard.no_data') }}</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>

View File

@@ -0,0 +1,44 @@
<div class="dashboard-panel">
<div class="grid-header">
<h4><i class="fa fa-comments-o" aria-hidden="true"></i>{{ trans('admin::dashboard.latest_reviews') }}</h4>
</div>
<div class="clearfix"></div>
<div class="table-responsive anchor-table">
<table class="table">
<thead>
<tr>
<th>{{ trans('admin::dashboard.table.latest_reviews.product') }}</th>
<th>{{ trans('admin::dashboard.table.customer') }}</th>
<th>{{ trans('admin::dashboard.table.latest_reviews.rating') }}</th>
</tr>
</thead>
<tbody>
@forelse ($latestReviews as $latestReview)
<tr>
<td>
<a href="{{ route('admin.reviews.edit', $latestReview) }}">
{{ $latestReview->product->name }}
</a>
</td>
<td>
<a href="{{ route('admin.reviews.edit', $latestReview) }}">
{{ $latestReview->reviewer_name }}
</a>
</td>
<td>
<a href="{{ route('admin.reviews.edit', $latestReview) }}">
{{ $latestReview->rating }}
</a>
</td>
</tr>
@empty
<tr>
<td class="empty" colspan="5">{{ trans('admin::dashboard.no_data') }}</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>

View File

@@ -0,0 +1,32 @@
<div class="dashboard-panel">
<div class="grid-header">
<h4><i class="fa fa-search" aria-hidden="true"></i>{{ trans('admin::dashboard.latest_search_terms') }}</h4>
</div>
<div class="clearfix"></div>
<div class="table-responsive search-terms">
<table class="table">
<thead>
<tr>
<th>{{ trans('admin::dashboard.table.latest_search_terms.keyword') }}</th>
<th>{{ trans('admin::dashboard.table.latest_search_terms.results') }}</th>
<th>{{ trans('admin::dashboard.table.latest_search_terms.hits') }}</th>
</tr>
</thead>
<tbody>
@forelse ($latestSearchTerms as $latestSearchTerm)
<tr>
<td>{{ $latestSearchTerm->term }}</td>
<td>{{ $latestSearchTerm->results }}</td>
<td>{{ $latestSearchTerm->hits }}</td>
</tr>
@empty
<tr>
<td class="empty" colspan="5">{{ trans('admin::dashboard.no_data') }}</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>

View File

@@ -0,0 +1,18 @@
<div class="sales-analytics">
<div class="grid-header clearfix">
<h4>
<i class="fa fa-bar-chart" aria-hidden="true"></i>{{ trans('admin::dashboard.sales_analytics_title') }}
</h4>
</div>
<div class="canvas">
<canvas class="chart" width="400" height="250"></canvas>
</div>
</div>
@push('globals')
<script>
FleetCart.langs['admin::dashboard.sales_analytics.orders'] = '{{ trans('admin::dashboard.sales_analytics.orders') }}';
FleetCart.langs['admin::dashboard.sales_analytics.sales'] = '{{ trans('admin::dashboard.sales_analytics.sales') }}';
</script>
@endpush

View File

@@ -0,0 +1,7 @@
<div class="world-map">
<div class="grid-header clearfix">
<h4><i class="fa fa-globe" aria-hidden="true"></i>World Map</h4>
</div>
<div id="world-map" style="width: 600px; height: 305px;"></div>
</div>

View File

@@ -0,0 +1,7 @@
<div class="form-group">
<div class="{{ ($buttonOffset ?? true) ? 'col-md-offset-2' : '' }} col-md-10">
<button type="submit" class="btn btn-primary" data-loading>
{{ trans('admin::admin.buttons.save') }}
</button>
</div>
</div>

View File

@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="{{ locale() }}">
<head>
<base href="{{ url('/') }}">
<meta charset="UTF-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>
@yield('title') - {{ setting('store_name') }} Admin
</title>
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:600|Roboto:400,500" rel="stylesheet">
@foreach ($assets->allCss() as $css)
<link media="all" type="text/css" rel="stylesheet" href="{{ v($css) }}">
@endforeach
@stack('styles')
@include('admin::partials.globals')
</head>
<body class="skin-blue sidebar-mini offcanvas clearfix {{ is_rtl() ? 'rtl' : 'ltr' }}">
<div class="left-side"></div>
@include('admin::partials.sidebar')
<div class="wrapper">
<div class="content-wrapper">
@include('admin::partials.top_nav')
<section class="content-header clearfix">
@yield('content_header')
</section>
<section class="content">
@include('admin::partials.notification')
@yield('content')
</section>
<div id="notification-toast"></div>
</div>
</div>
@include('admin::partials.footer')
@include('admin::partials.confirmation_modal')
@foreach ($assets->allJs() as $js)
<script src="{{ v($js) }}"></script>
@endforeach
@stack('scripts')
</body>
</html>

View File

@@ -0,0 +1,17 @@
@if ($paginator->hasPages())
<ul class="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<li class="disabled"><span>{{ trans('admin::admin.pagination.previous') }}</span></li>
@else
<li><a href="{{ $paginator->previousPageUrl() }}" rel="prev">{{ trans('admin::admin.pagination.previous') }}</a></li>
@endif
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<li><a href="{{ $paginator->nextPageUrl() }}" rel="next">{{ trans('admin::admin.pagination.next') }}</a></li>
@else
<li class="disabled"><span>{{ trans('admin::admin.pagination.next') }}</span></li>
@endif
</ul>
@endif

View File

@@ -0,0 +1,34 @@
<div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<i class="fa fa-times" aria-hidden="true"></i>
</button>
<h3 class="modal-title">{{ trans('admin::admin.delete.confirmation') }}</h3>
</div>
<div class="modal-body">
<div class="default-message">
{{ $message ?? trans('admin::admin.delete.confirmation_message') }}
</div>
</div>
<div class="modal-footer">
<form method="POST" id="confirmation-form">
{{ csrf_field() }}
{{ method_field('delete') }}
<button type="button" class="btn btn-default cancel" data-dismiss="modal">
{{ trans('admin::admin.buttons.cancel') }}
</button>
<button type="submit" class="btn btn-danger delete">
{{ trans('admin::admin.buttons.delete') }}
</button>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,35 @@
<div class="modal fade" id="keyboard-shortcuts-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<a type="button" class="close" data-dismiss="modal" aria-label="Close">&times;</a>
<h4 class="modal-title">{{ trans('admin::admin.shortcuts.available_shortcuts') }}</h4>
</div>
<div class="modal-body">
<dl class="dl-horizontal">
<dt><code>?</code></dt>
<dd>{{ trans('admin::admin.shortcuts.this_menu') }}</dd>
</dl>
@stack('shortcuts')
</div>
</div>
</div>
</div>
<footer class="main-footer">
<div class="pull-right hidden-xs">
<span>v{{ fleetcart_version() }}</span>
</div>
<a href="#" data-toggle="modal" data-target="#keyboard-shortcuts-modal">
<i class="fa fa-keyboard-o"></i>
</a>&nbsp;
<span>
Copyright &copy; {{ date('Y') }} <a href="{{ route('home') }}"
target="_blank">{{ setting('store_name') }}</a>
</span>
</footer>

View File

@@ -0,0 +1,20 @@
<script>
window.FleetCart = {
version: '{{ fleetcart_version() }}',
csrfToken: '{{ csrf_token() }}',
baseUrl: '{{ url('/') }}',
rtl: {{ is_rtl() ? 'true' : 'false' }},
langs: {},
data: {},
errors: {},
selectize: [],
};
FleetCart.langs['admin::admin.buttons.delete'] = '{{ trans('admin::admin.buttons.delete') }}';
FleetCart.langs['media::media.file_manager.title'] = '{{ trans('media::media.file_manager.title') }}';
FleetCart.langs['media::messages.image_has_been_added'] = '{{ trans('media::messages.image_has_been_added') }}';
</script>
@stack('globals')
@routes

View File

@@ -0,0 +1,23 @@
@if (session()->has('success'))
<div class="alert alert-success fade in alert-dismissable clearfix">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<div class="alert-icon">
<i class="fa fa-check" aria-hidden="true"></i>
</div>
<span class="alert-text">{{ session('success') }}</span>
</div>
@endif
@if (session()->has('error'))
<div class="alert alert-danger fade in alert-dismissable clearfix">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<div class="alert-icon">
<i class="fa fa-exclamation" aria-hidden="true"></i>
</div>
<span class="alert-text">{{ session('error') }}</span>
</div>
@endif

View File

@@ -0,0 +1,15 @@
@push('globals')
<script>
FleetCart.selectize.push({
load: function (query, callback) {
var url = this.$input.data('url');
if (url === undefined || query.length === 0) {
return callback();
}
$.get(url + '?query=' + query, callback, 'json');
}
});
</script>
@endpush

View File

@@ -0,0 +1,15 @@
<aside class="main-sidebar">
<header class="main-header clearfix">
<a class="logo" href="{{ route('admin.dashboard.index') }}">
<span class="logo-lg">{{ setting('store_name') }}</span>
</a>
<a href="javascript:void(0);" class="sidebar-toggle" data-toggle="offcanvas" role="button">
<i aria-hidden="true" class="fa fa-bars"></i>
</a>
</header>
<section class="sidebar">
{!! $sidebar !!}
</section>
</aside>

View File

@@ -0,0 +1,4 @@
<div class="checkbox">
<input type="checkbox" class="select-row" value="{{ $entity->id }}" id="{{ $id = str_random() }}">
<label for="{{ $id }}"></label>
</div>

View File

@@ -0,0 +1,3 @@
<span data-toggle="tooltip" title="{{ is_null($date) ? '' : $date->toFormattedDateString() }}">
{!! is_null($date) ? '&mdash;' : $date->diffForHumans() !!}
</span>

View File

@@ -0,0 +1,7 @@
<div class="thumbnail-holder">
@if ($file->exists)
<img src="{{ $file->path }}" alt="thumbnail">
@else
<i class="fa fa-picture-o"></i>
@endif
</div>

View File

@@ -0,0 +1,6 @@
<th>
<div class="checkbox">
<input type="checkbox" class="select-all" id="{{ $name ?? '' }}-select-all">
<label for="{{ $name ?? '' }}-select-all"></label>
</div>
</th>

View File

@@ -0,0 +1,37 @@
<nav class="navbar navbar-static-top clearfix">
<ul class="nav navbar-nav clearfix">
<li class="visit-store hidden-sm hidden-xs">
<a href="{{ route('home') }}">
<i class="fa fa-desktop"></i>
{{ trans('admin::admin.visit_store') }}
</a>
</li>
<li class="dropdown top-nav-menu pull-right">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-user-circle-o"></i><span>{{ $currentUser->first_name }}</span>
</a>
<ul class="dropdown-menu">
<li><a href="{{ route('admin.profile.edit') }}">{{ trans('user::users.profile') }}</a></li>
<li><a href="{{ route('admin.logout') }}">{{ trans('user::auth.logout') }}</a></li>
</ul>
</li>
@if (count(supported_locales()) > 1)
<li class="language dropdown top-nav-menu pull-right">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span>{{ strtoupper(locale()) }}</span>
</a>
<ul class="dropdown-menu">
@foreach (supported_locales() as $locale => $language)
<li class="{{ $locale === locale() ? 'active' : '' }}">
<a href="{{ localized_url($locale) }}">{{ $language['name'] }}</a>
</li>
@endforeach
</ul>
</li>
@endif
</ul>
</nav>

View File

@@ -0,0 +1,11 @@
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', 'DashboardController@index')->name('admin.dashboard.index');
Route::get('/sales-analytics', [
'as' => 'admin.sales_analytics.index',
'uses' => 'SalesAnalyticsController@index',
'middleware' => 'can:admin.orders.index',
]);

View File

@@ -0,0 +1,91 @@
<?php
namespace Modules\Admin\Sidebar;
use Maatwebsite\Sidebar\Menu;
use Maatwebsite\Sidebar\Sidebar;
use Nwidart\Modules\Facades\Module;
use Nwidart\Modules\Contracts\RepositoryInterface as Modules;
class AdminSidebar implements Sidebar
{
/**
* The menu instance.
*
* @var \Maatwebsite\Sidebar\Menu
*/
protected $menu;
/**
* Create a new sidebar instance.
*
* @param \Maatwebsite\Sidebar\Menu $menu
* @return void
*/
public function __construct(Menu $menu)
{
$this->menu = $menu;
}
/**
* Get the built menu.
*
* @return \Maatwebsite\Sidebar\Menu
*/
public function getMenu()
{
$this->build();
return $this->menu;
}
/**
* Build the sidebar menu.
*
* @return void
*/
public function build()
{
$this->addActiveThemeExtender();
$this->addModuleExtenders();
}
/**
* Add active theme's sidebar extender.
*
* @return void
*/
private function addActiveThemeExtender()
{
$theme = setting('active_theme');
$this->add("Themes\\{$theme}\\Sidebar\\SidebarExtender");
}
/**
* Add all enabled modules sidebar extender.
*
* @return void
*/
private function addModuleExtenders()
{
foreach (Module::allEnabled() as $module) {
$this->add("Modules\\{$module->getName()}\\Sidebar\\SidebarExtender");
}
}
/**
* Add sidebar extender to the menu.
*
* @param string $extender
* @return void
*/
private function add($extender)
{
if (class_exists($extender)) {
resolve($extender)->extend($this->menu);
}
$this->menu->add($this->menu);
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Modules\Admin\Sidebar;
use Modules\User\Contracts\Authentication;
class BaseSidebarExtender
{
protected $auth;
public function __construct(Authentication $auth)
{
$this->auth = $auth;
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Modules\Admin\Sidebar;
use Maatwebsite\Sidebar\Item;
use Maatwebsite\Sidebar\Menu;
use Maatwebsite\Sidebar\Group;
class SidebarExtender extends BaseSidebarExtender
{
public function extend(Menu $menu)
{
$menu->group(trans('admin::sidebar.content'), function (Group $group) {
$group->weight(5);
$group->hideHeading();
$group->item(trans('admin::dashboard.dashboard'), function (Item $item) {
$item->icon('fa fa-dashboard');
$item->route('admin.dashboard.index');
$item->isActiveWhen(route('admin.dashboard.index', null, false));
});
});
$menu->group(trans('admin::sidebar.system'), function (Group $group) {
$group->weight(10);
$group->item(trans('admin::sidebar.appearance'), function (Item $item) {
$item->icon('fa fa-paint-brush');
$item->weight(15);
$item->route('admin.sliders.index');
$item->authorize(
$this->auth->hasAnyAccess(['admin.sliders.index', 'admin.storefront.edit'])
);
});
$group->item(trans('admin::sidebar.tools'), function (Item $item) {
$item->icon('fa fa-wrench');
$item->weight(20);
$item->route('admin.importer.index');
$item->authorize(
$this->auth->hasAnyAccess(['admin.importer.index'])
);
});
});
}
}

View File

@@ -0,0 +1,298 @@
<?php
namespace Modules\Admin\Traits;
use Illuminate\Http\Request;
use Modules\Support\Search\Searchable;
use Modules\Admin\Ui\Facades\TabManager;
trait HasCrudActions
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
if ($request->has('query')) {
return $this->getModel()
->search($request->get('query'))
->query()
->limit($request->get('limit', 10))
->get();
}
if ($request->has('table')) {
return $this->getModel()->table($request);
}
return view("{$this->viewPath}.index");
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$data = array_merge([
'tabs' => TabManager::get($this->getModel()->getTable()),
$this->getResourceName() => $this->getModel(),
], $this->getFormData('create'));
return view("{$this->viewPath}.create", $data);
}
/**
* Store a newly created resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function store()
{
$this->disableSearchSyncing();
$entity = $this->getModel()->create(
$this->getRequest('store')->all()
);
$this->searchable($entity);
if (method_exists($this, 'redirectTo')) {
return $this->redirectTo($entity);
}
return redirect()->route("{$this->getRoutePrefix()}.index")
->withSuccess(trans('admin::messages.resource_saved', ['resource' => $this->getLabel()]));
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$entity = $this->getEntity($id);
if (request()->wantsJson()) {
return $entity;
}
return view("{$this->viewPath}.show")->with($this->getResourceName(), $entity);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
$data = array_merge([
'tabs' => TabManager::get($this->getModel()->getTable()),
$this->getResourceName() => $this->getEntity($id),
], $this->getFormData('edit', $id));
return view("{$this->viewPath}.edit", $data);
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update($id)
{
$entity = $this->getEntity($id);
$this->disableSearchSyncing();
$entity->update(
$this->getRequest('update')->all()
);
$this->searchable($entity);
if (method_exists($this, 'redirectTo')) {
return $this->redirectTo($entity)
->withSuccess(trans('admin::messages.resource_saved', ['resource' => $this->getLabel()]));
}
return redirect()->route("{$this->getRoutePrefix()}.index")
->withSuccess(trans('admin::messages.resource_saved', ['resource' => $this->getLabel()]));
}
/**
* Destroy resources by given ids.
*
* @param string $ids
* @return void
*/
public function destroy($ids)
{
$this->getModel()
->withoutGlobalScope('active')
->whereIn('id', explode(',', $ids))
->delete();
}
/**
* Get an entity by the given id.
*
* @param int $id
* @return \Illuminate\Database\Eloquent\Model
*/
protected function getEntity($id)
{
return $this->getModel()
->with($this->relations())
->withoutGlobalScope('active')
->findOrFail($id);
}
/**
* Get the relations that should be eager loaded.
*
* @return array
*/
private function relations()
{
return collect($this->with ?? [])->mapWithKeys(function ($relation) {
return [$relation => function ($query) {
return $query->withoutGlobalScope('active');
}];
})->all();
}
/**
* Get form data for the given action.
*
* @param string $action
* @param mixed ...$args
* @return array
*/
protected function getFormData($action, ...$args)
{
if (method_exists($this, 'formData')) {
return $this->formData(...$args);
}
if ($action === 'create' && method_exists($this, 'createFormData')) {
return $this->createFormData();
}
if ($action === 'edit' && method_exists($this, 'editFormData')) {
return $this->editFormData(...$args);
}
return [];
}
/**
* Get name of the resource.
*
* @return string
*/
protected function getResourceName()
{
if (isset($this->resourceName)) {
return $this->resourceName;
}
return lcfirst(class_basename($this->model));
}
/**
* Get label of the resource.
*
* @return void
*/
protected function getLabel()
{
return trans($this->label);
}
/**
* Get route prefix of the resource.
*
* @return string
*/
protected function getRoutePrefix()
{
if (isset($this->routePrefix)) {
return $this->routePrefix;
}
return "admin.{$this->getModel()->getTable()}";
}
/**
* Get a new instance of the model.
*
* @return \Modules\Support\Eloquent\Model
*/
protected function getModel()
{
return new $this->model;
}
/**
* Get request object
*
* @param string $action
* @return \Illuminate\Http\Request
*/
protected function getRequest($action)
{
if (! isset($this->validation)) {
return request();
}
if (isset($this->validation[$action])) {
return resolve($this->validation[$action]);
}
return resolve($this->validation);
}
/**
* Disable search syncing for the entity.
*
* @return void
*/
protected function disableSearchSyncing()
{
if ($this->isSearchable()) {
$this->getModel()->disableSearchSyncing();
}
}
/**
* Determine if the entity is searchable.
*
* @return bool
*/
protected function isSearchable()
{
return in_array(Searchable::class, class_uses_recursive($this->getModel()));
}
/**
* Make the given model instance searchable.
*
* @return void
*/
protected function searchable($entity)
{
if ($this->isSearchable($entity)) {
$entity->searchable();
}
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Modules\Admin\Ui;
use Illuminate\Contracts\Support\Responsable;
class AdminTable implements Responsable
{
/**
* Raw columns that will not be escaped.
*
* @var array
*/
protected $rawColumns = [];
/**
* Raw columns that will not be escaped.
*
* @var array
*/
protected $defaultRawColumns = [
'checkbox', 'thumbnail', 'status', 'created',
];
/**
* Source of the table.
*
* @var \Illuminate\Database\Eloquent\Builder
*/
protected $source;
/**
* Create a new table instance.
*
* @param \Illuminate\Database\Eloquent\Builder $source
* @return void
*/
public function __construct($source = null)
{
$this->source = $source;
}
/**
* Make table response for the resource.
*
* @param mixed $source
* @return \Illuminate\Http\JsonResponse
*/
public function make()
{
return $this->newTable();
}
/**
* Create a new datatable instance;
*
* @param mixed $source
* @return \Yajra\DataTables\DataTables
*/
public function newTable()
{
return datatables($this->source)
->addColumn('checkbox', function ($entity) {
return view('admin::partials.table.checkbox', compact('entity'));
})
->editColumn('status', function ($entity) {
return $entity->is_active
? '<span class="dot green"></span>'
: '<span class="dot red"></span>';
})
->editColumn('created', function ($entity) {
return view('admin::partials.table.date')->with('date', $entity->created_at);
})
->rawColumns(array_merge($this->defaultRawColumns, $this->rawColumns))
->removeColumn('translations');
}
/**
* Create an HTTP response that represents the object.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function toResponse($request)
{
return $this->make()->toJson();
}
}

View File

@@ -0,0 +1,225 @@
<?php
namespace Modules\Admin\Ui\Concerns;
use LogicException;
use Modules\Support\Money;
use Illuminate\Support\Collection;
use Illuminate\Support\HtmlString;
use Illuminate\Database\Eloquent\Relations\Relation;
trait InputFields
{
protected function inputField($name, $value, $class, $attributes, $options)
{
$readonly = array_pull($options, 'readonly', false);
$disabled = array_get($options, 'disabled', false);
return "<input
name='{$name}'
class='form-control {$class}'
id='{$name}'
value='{$value}'
{$attributes}"
. ($disabled ? 'disabled' : '')
. ($readonly ? 'readonly ' : '') .
'>';
}
protected function textareaField($name, $value, $class, $attributes, $options)
{
$readonly = array_pull($options, 'readonly', false);
$disabled = array_get($options, 'disabled', false);
return "<textarea
name='{$name}'
class='form-control {$class}'
id='{$name}'
{$attributes}"
. ($disabled ? 'disabled' : '')
. ($readonly ? 'readonly ' : '') .
">{$value}</textarea>";
}
protected function checkboxField($name, $value, $class, $attributes, $options, $label)
{
$checked = array_pull($options, 'checked', false);
$disabled = array_get($options, 'disabled', false);
if (! is_null($value)) {
$checked = $value;
}
$html = '<div class="checkbox">';
if (! $disabled) {
$html .= "<input type='hidden' value='0' name='{$name}'>";
}
$html .= "<input
type='checkbox'
name='{$name}'
class='{$class}'
id='{$name}'
{$attributes}
value='1'"
. ($checked ? 'checked ' : '')
. ($disabled ? 'disabled' : '') .
'>';
$html .= "<label for='{$name}'>{$label}</label>";
$html .= '</div>';
return $html;
}
protected function selectField($name, $value, $class, $attributes, $options, $list)
{
$multiple = array_get($options, 'multiple', false);
$disabled = array_get($options, 'disabled', false);
$readonly = array_pull($options, 'readonly', false);
$html = "<select
name='{$name}'
class='form-control custom-select-black {$class}'
id='{$name}'
{$attributes}"
. ($disabled ? 'disabled' : '')
. ($readonly ? 'readonly ' : '') .
'>';
foreach ($list as $listValue => $listName) {
$listValue = e($listValue);
$listName = e($listName);
if ($multiple && $value instanceof Collection) {
$selected = $value->where('id', $listValue)->isNotEmpty() ? 'selected' : '';
} elseif ($multiple && is_array($value)) {
$selected = in_array($listValue, $value) ? 'selected' : '';
} else {
$selected = (! is_null($value) && $value == $listValue) ? 'selected' : '';
}
$html .= "<option value='{$listValue}' {$selected}>{$listName}</option>";
}
$html .= '</select>';
return $html;
}
protected function field($name, $title, $errors, $entity, $options, callable $fieldCallback, ...$args)
{
$value = $this->getValue($entity, $name);
if (is_string($value)) {
$value = e($value);
}
$normalizedName = $this->normalizeTranslatableFieldName($name);
$name = array_get($options, 'multiple', false) ? "{$name}[]" : $name;
$required = array_pull($options, 'required', false);
$help = array_pull($options, 'help', false);
$params = array_merge([
$name,
$value,
array_pull($options, 'class'),
$this->generateHtmlAttributes($options),
$options,
], $args);
$labelCol = array_pull($options, 'labelCol', 3);
$fieldCol = 12 - $labelCol;
$html = '<div class="form-group">';
$html .= $this->label($name, $title, $labelCol, $required);
$html .= "<div class='col-md-{$fieldCol}'>";
$html .= call_user_func_array($fieldCallback, $params);
if ($help && ! $errors->has($normalizedName)) {
$html .= "<span class='help-block'>{$help}</span>";
}
$html .= $errors->first($normalizedName, '<span class="help-block text-red">:message</span>');
$html .= '</div>';
$html .= '</div>';
return new HtmlString($html);
}
private function normalizeTranslatableFieldName($name)
{
if (starts_with($name, 'translatable[')) {
return 'translatable.' . str_between($name, 'translatable[', ']');
}
return $name;
}
protected function label($name, $title, $labelCol = 3, $required = false)
{
$html = "<label for='{$name}' class='col-md-{$labelCol} control-label text-left'>{$title}";
if ($required) {
$html .= '<span class="m-l-5 text-red">*</span>';
}
return $html .= '</label>';
}
private function getValue($entity, $name)
{
if (is_object($entity) && method_exists($entity, 'translate') && $entity->isTranslationAttribute($name)) {
$translatedValue = optional($entity->translate(locale(), false))->$name;
return old($name, $translatedValue);
}
$camelCaseName = camel_case($name);
if (is_object($entity) && method_exists($entity, $camelCaseName) && $entity->{$camelCaseName}() instanceof Relation) {
$name = $camelCaseName;
}
$normalizedName = $this->normalizeTranslatableFieldName($name);
$name = str_between($name, 'translatable[', ']');
try {
$value = data_get($entity, $name);
} catch (LogicException $e) {
$value = $entity->getOriginal('url');
}
if ($value instanceof Money) {
$value = $value->amount();
}
return old($normalizedName, $value);
}
protected function generateHtmlAttributes($options = [])
{
$this->unsetUnnecessaryAttributes($options);
$attributes = '';
foreach ($options as $attr => $value) {
$attributes .= "{$attr}='{$value}' ";
}
return $attributes;
}
protected function unsetUnnecessaryAttributes(&$options = [])
{
foreach ($this->unnecessaryAttributes as $attribute) {
if (array_key_exists($attribute, $options)) {
unset($options[$attribute]);
}
}
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Modules\Admin\Ui\Facades;
use Illuminate\Support\Facades\Facade;
use Modules\Admin\Ui\Form as FormBuilder;
class Form extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return FormBuilder::class;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Modules\Admin\Ui\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @method static void register(string $name, string $tabsClass)
* @method static void extend(string $name, string $extenderClass)
*
* @see \Modules\Admin\Ui\TabManager
*/
class TabManager extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return \Modules\Admin\Ui\TabManager::class;
}
}

71
Modules/Admin/Ui/Form.php Normal file
View File

@@ -0,0 +1,71 @@
<?php
namespace Modules\Admin\Ui;
use Modules\Admin\Ui\Concerns\InputFields;
class Form
{
use InputFields;
protected $unnecessaryAttributes = ['disabled', 'readonly', 'checked'];
public function text($name, $title, $errors, $entity = null, $options = [])
{
return $this->input($name, $title, $errors, $entity, array_merge($options, ['type' => 'text']));
}
public function password($name, $title, $errors, $entity = null, $options = [])
{
return $this->input($name, $title, $errors, $entity, array_merge($options, ['type' => 'password']));
}
public function number($name, $title, $errors, $entity = null, $options = [])
{
return $this->input($name, $title, $errors, $entity, array_merge($options, ['type' => 'number']));
}
public function email($name, $title, $errors, $entity = null, $options = [])
{
return $this->input($name, $title, $errors, $entity, array_merge($options, ['type' => 'email']));
}
public function file($name, $title, $errors, $entity = null, $options = [])
{
return $this->input($name, $title, $errors, $entity, array_merge($options, ['type' => 'file']));
}
public function color($name, $title, $errors, $entity = null, $options = [])
{
return $this->input($name, $title, $errors, $entity, array_merge($options, ['type' => 'color']));
}
public function input($name, $title, $errors, $entity = null, $options = [])
{
return $this->field($name, $title, $errors, $entity, $options, [$this, 'inputField']);
}
public function textarea($name, $title, $errors, $entity = null, $options = [])
{
$options = array_merge(['rows' => 10, 'cols' => 10], $options);
return $this->field($name, $title, $errors, $entity, $options, [$this, 'textareaField']);
}
public function wysiwyg($name, $title, $errors, $entity = null, $options = [])
{
$options['class'] = array_get($options, 'class', '') . ' wysiwyg';
return $this->textarea($name, $title, $errors, $entity, $options);
}
public function checkbox($name, $title, $label, $errors, $entity = null, $options = [])
{
return $this->field($name, $title, $errors, $entity, $options, [$this, 'checkboxField'], $label);
}
public function select($name, $title, $errors, $list = [], $entity = null, $options = [])
{
return $this->field($name, $title, $errors, $entity, $options, [$this, 'selectField'], $list);
}
}

204
Modules/Admin/Ui/Tab.php Normal file
View File

@@ -0,0 +1,204 @@
<?php
namespace Modules\Admin\Ui;
use Illuminate\Support\ViewErrorBag;
class Tab
{
/**
* Active state of the tab.
*
* @var bool
*/
private $active = false;
/**
* Name of the tab.
*
* @var string
*/
private $name;
/**
* Label of the tab.
*
* @var string
*/
private $label;
/**
* Weight of the tab.
*
* @var int
*/
private $weight = 0;
/**
* Available fields on the tab.
*
* @var array
*/
private $fields = [];
/**
* View of the tab.
*
* @var string|\Closure
*/
private $view;
/**
* Data for the tab view.
*
* @var array
*/
private $data;
/**
* Error message bag.
*
* @var \Illuminate\Support\ViewErrorBag
*/
private $errors;
/**
* Create a new Tab instance.
*
* @param string $name
* @param string $label
* @return void
*/
public function __construct($name, $label)
{
$this->name = $name;
$this->label = $label;
$this->errors = request()->session()->get('errors') ?: new ViewErrorBag;
}
/**
* Set tab as active.
*
* @return self
*/
public function active()
{
$this->active = true;
return $this;
}
/**
* Set weight of tab.
*
* @param int $weight
* @return self
*/
public function weight($weight)
{
$this->weight = $weight;
return $this;
}
/**
* Get weight of the tab.
*
* @return int
*/
public function getWeight()
{
return $this->weight;
}
/**
* Get nav of the tab.
*
* @param array $data
* @return string
*/
public function getNav()
{
return "<li class='{$this->activeClass()} {$this->errorClass()}'>
<a href='#{$this->name}' data-toggle='tab'>{$this->label}</a>
</li>";
}
/**
* Return active class if tab is active.
*
* @return string
*/
private function activeClass()
{
return $this->active ? 'active' : '';
}
/**
* Return error class if tab fields has any error.
*
* @return string
*/
private function errorClass()
{
return $this->errors->hasAny($this->fields) ? 'has-error' : '';
}
/**
* Set fields of the tab.
*
* @param array|string $fields
* @return self
*/
public function fields($fields)
{
$this->fields = is_array($fields) ? $fields : func_get_args();
return $this;
}
/**
* Get fields of the tab.
*
* @return array
*/
public function getFields()
{
return $this->fields;
}
/**
* Set view of the tab.
*
* @param \Closure|string $view
* @param array $data
* @return self
*/
public function view($view, $data = [])
{
$this->view = $view;
$this->data = $data;
return $this;
}
/**
* Get view of the tab.
*
* @param array $data
* @return string
*/
public function getView($data = [])
{
$html = "<div class='tab-pane fade in {$this->activeClass()}' id='{$this->name}'>";
$html .= "<h3 class='tab-content-title'>{$this->label}</h3>";
if (is_callable($this->view)) {
$html .= call_user_func($this->view, array_merge($this->data, $data));
} else {
$html .= view($this->view)->with(array_merge($this->data, $data))->render();
}
return $html .= '</div>';
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Modules\Admin\Ui;
class TabManager
{
/**
* The array of all Tabs.
*
* @var array
*/
private $tabs = [];
/**
* The array of all tabs extenders.
*
* @var array
*/
private $extends = [];
/**
* Register a new Tabs.
*
* @param string $name
* @param string $tabs
* @return void
*/
public function register($name, $tabs)
{
$this->tabs[$name] = $tabs;
}
/**
* Add a new Tabs extender.
*
* @param string $name
* @param string $extender
* @return void
*/
public function extend($name, $extender)
{
$this->extends[$name][] = $extender;
}
/**
* Get tabs for the given name.
*
* @param string $name
* @return \Modules\Admin\Ui\Tabs
*/
public function get($name)
{
if (! array_key_exists($name, $this->tabs)) {
return;
}
return tap(resolve($this->tabs[$name]), function (Tabs $tabs) use ($name) {
$tabs->make();
$this->extendTabs($tabs, array_get($this->extends, $name, []));
});
}
/**
* Extend the given tabs using the given extenders.
*
* @param \Modules\Admin\Ui\Tabs $tabs
* @param array $extenders
* @return void
*/
private function extendTabs(Tabs $tabs, array $extenders)
{
foreach ($extenders as $extender) {
resolve($extender)->extend($tabs);
}
}
}

223
Modules/Admin/Ui/Tabs.php Normal file
View File

@@ -0,0 +1,223 @@
<?php
namespace Modules\Admin\Ui;
use InvalidArgumentException;
use Illuminate\Support\HtmlString;
use Illuminate\Support\ViewErrorBag;
abstract class Tabs
{
/**
* Array of all groups.
*
* @var array
*/
protected $groups = [];
/**
* Current group name of the tabs.
*
* @var string
*/
protected $group;
/**
* Array of all tabs.
*
* @var array
*/
protected $tabs = [];
/**
* Indicate that submit button should add offset class.
*
* @var bool
*/
protected $buttonOffset = true;
/**
* Make new tabs with groups.
*
* @return void
*/
abstract public function make();
/**
* Set group name.
*
* @param string $name
* @param string $title
* @return self
*/
public function group($name, $title = null)
{
$this->group = $name;
if (! is_null($title)) {
$this->groups[$name]['title'] = $title;
}
return $this;
}
/**
* Set current group as active group.
*
* @return self
*/
public function active()
{
$this->groups[$this->group]['active'] = true;
return $this;
}
/**
* Add new tab.
*
* @param \Modules\Admin\Ui\Tab|null $tab
* @return void
*/
public function add($tab)
{
if (! is_null($tab)) {
$this->tabs[$this->group][] = $tab;
}
return $this;
}
/**
* Determine if tabs fields has any error.
*
* @return bool
*/
public function hasError()
{
return $this->getErrors()->hasAny($this->getTabFields());
}
/**
* Get error message bag.
*
* @return \Illuminate\Support\ViewErrorBag
*/
protected function getErrors()
{
return request()->session()->get('errors') ?: new ViewErrorBag;
}
/**
* Get all tabs fields.
*
* @return array
*/
protected function getTabFields()
{
return array_reduce($this->getSortedTabs(), function ($fields, Tab $tab) {
return array_merge($fields, $tab->getFields());
}, []);
}
/**
* Generate navs for the tabs.
*
* @param array $data
* @return \Illuminate\Support\HtmlString
*/
public function navs($data = [])
{
return new HtmlString($this->getTabsData('nav', $data));
}
/**
* Render the tabs,
*
* @param array $data
* @return void
*/
public function render($data = [])
{
return view('admin::components.accordion', [
'tabs' => $this,
'name' => class_basename($this),
'groups' => $this->groups(),
'contents' => $this->contents($data),
'buttonOffset' => $this->buttonOffset,
]);
}
/**
* Get all groups with it's options.
*
* @return array
*/
protected function groups()
{
$groups = [];
foreach ($this->groups as $group => $options) {
$groups[$group] = $options;
}
return $groups;
}
/**
* Generate contents for the tabs.
*
* @param array $data
* @return \Illuminate\Support\HtmlString
*/
protected function contents($data = [])
{
$contents = '';
foreach ($this->groups as $group => $options) {
$contents .= $this->group($group)->getTabsData('view', $data);
}
return new HtmlString($contents);
}
/**
* Get tabs data for the given type.
*
* @param string $type
* @param array $data
* @return string
*
* @throws \InvalidArgumentException
*/
protected function getTabsData($type, $data = [])
{
if (! array_key_exists($this->group, $this->tabs)) {
throw new InvalidArgumentException("Group [$this->group] is not registered.");
}
$html = '';
foreach ($this->getSortedTabs() as $tab) {
$method = 'get' . ucfirst($type);
if (method_exists($tab, $method)) {
$html .= call_user_func_array([$tab, $method], [$data]);
}
}
return $html;
}
/**
* Get sorted tabs.
*
* @return array
*/
protected function getSortedTabs()
{
return collect($this->tabs[$this->group])->sortBy(function (Tab $tab) {
return $tab->getWeight();
})->all();
}
}

Some files were not shown because too many files have changed in this diff Show More