Generated Datatable Action Menus (#2590)

* Style Datatable

* Component Actions

* Harvest user permissions from Pivot table

* Pad out permissions

* Client actions

* Fixes for travis

* Client Datatables

* Menu permissions

* Tests for menu permissions

* Action menu

* Implement query builder filter

* Flatten user permissions

* Implement rendering of client action dropdowns

* Generated Action Menus
This commit is contained in:
David Bomba 2019-01-07 22:30:28 +11:00 committed by GitHub
parent 43342fb98b
commit 0faf91dd5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1142 additions and 187 deletions

View File

@ -54,6 +54,7 @@ before_script:
- php artisan migrate --database=db-ninja-02 --seed --no-interaction
- php artisan optimize
- npm install
- npm install @types/bluebird @types/core-js@0.9.36
- npm run production
# migrate and seed the database
# Start webserver on ninja.test:8000

View File

@ -6,7 +6,7 @@
[![codecov](https://codecov.io/gh/invoiceninja/invoiceninja/branch/v5.0/graph/badge.svg)](https://codecov.io/gh/invoiceninja/invoiceninja)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/d39acb4bf0f74a0698dc77f382769ba5)](https://www.codacy.com/app/turbo124/invoiceninja?utm_source=github.com&utm_medium=referral&utm_content=invoiceninja/invoiceninja&utm_campaign=Badge_Grade)
**Invoice Ninja v 5.0** is coming soon!
**Invoice Ninja v 2.0** is coming soon!
We will be using the lessons learnt in Invoice Ninja 4.0 to build a bigger better platform to work from. If you would like to contribute to the project we will gladly accept contributions for code, user guides, bug tracking and feedback! Please consider the following guidelines prior to submitting a pull request:
@ -22,8 +22,8 @@ Where practical code should be strongly typed, ie your methods must return a typ
`public function doThis() : void`
PHP > 7.1 allows the return type Nullable so there should be no circumstance a type cannot be return by using the following:
PHP >= 7.1 allows the return type Nullable so there should be no circumstance a type cannot be return by using the following:
`public function doThat() ?:string`
Please include tests with PRs to ensure your code works well and integrates with the rest of the project. Please ensure suitable unit/functional/acceptance tests are included to provide code coverage.
To improve chances of PRs being merged please include teststo ensure your code works well and integrates with the rest of the project.

View File

@ -3,94 +3,158 @@
namespace App\Datatables;
use App\Models\Client;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\UserSessionAttributes;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ClientDatatable
class ClientDatatable extends EntityDatatable
{
use MakesHash;
use MakesActionMenu;
/**
* ?sort=&page=1&per_page=20
*/
public static function query(Request $request, int $company_id)
{
/**
*
* $sort_col is returned col|asc
* needs to be exploded
*
*/
$sort_col = explode("|", $request->input('sort'));
/**
* ?sort=&page=1&per_page=20
*/
public function query(Request $request, int $company_id)
{
/**
*
* $sort_col is returned col|asc
* needs to be exploded
*
*/
$sort_col = explode("|", $request->input('sort'));
return response()->json(self::find($company_id, $request->input('filter'))->orderBy($sort_col[0], $sort_col[1])->paginate($request->input('per_page')), 200);
}
$data = $this->find($company_id, $request->input('filter'))
->orderBy($sort_col[0], $sort_col[1])
->paginate($request->input('per_page'));
return response()
->json($this->buildActionColumn($data), 200);
}
private static function find(int $company_id, $filter, $userId = false)
{
$query = DB::table('clients')
->join('companies', 'companies.id', '=', 'clients.company_id')
->join('client_contacts', 'client_contacts.client_id', '=', 'clients.id')
->where('clients.company_id', '=', $company_id)
->where('client_contacts.is_primary', '=', true)
->where('client_contacts.deleted_at', '=', null)
//->whereRaw('(clients.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
->select(
DB::raw('COALESCE(clients.currency_id, companies.currency_id) currency_id'),
DB::raw('COALESCE(clients.country_id, companies.country_id) country_id'),
DB::raw("CONCAT(COALESCE(client_contacts.first_name, ''), ' ', COALESCE(client_contacts.last_name, '')) contact"),
'clients.id',
'clients.name',
'clients.private_notes',
'client_contacts.first_name',
'client_contacts.last_name',
'clients.balance',
'clients.last_login',
'clients.created_at',
'clients.created_at as client_created_at',
'client_contacts.phone',
'client_contacts.email',
'clients.deleted_at',
'clients.is_deleted',
'clients.user_id',
'clients.id_number'
);
private function find(int $company_id, $filter, $userId = false)
{
$query = DB::table('clients')
->join('companies', 'companies.id', '=', 'clients.company_id')
->join('client_contacts', 'client_contacts.client_id', '=', 'clients.id')
->where('clients.company_id', '=', $company_id)
->where('client_contacts.is_primary', '=', true)
->where('client_contacts.deleted_at', '=', null)
//->whereRaw('(clients.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
->select(
DB::raw('COALESCE(clients.currency_id, companies.currency_id) currency_id'),
DB::raw('COALESCE(clients.country_id, companies.country_id) country_id'),
DB::raw("CONCAT(COALESCE(client_contacts.first_name, ''), ' ', COALESCE(client_contacts.last_name, '')) contact"),
'clients.id',
'clients.name',
'clients.private_notes',
'client_contacts.first_name',
'client_contacts.last_name',
'clients.balance',
'clients.last_login',
'clients.created_at',
'clients.created_at as client_created_at',
'client_contacts.phone',
'client_contacts.email',
'clients.deleted_at',
'clients.is_deleted',
'clients.user_id',
'clients.id_number'
);
/*
if(Auth::user()->account->customFieldsOption('client1_filter')) {
$query->addSelect('clients.custom_value1');
}
if(Auth::user()->account->customFieldsOption('client1_filter')) {
$query->addSelect('clients.custom_value1');
}
if(Auth::user()->account->customFieldsOption('client2_filter')) {
$query->addSelect('clients.custom_value2');
}
if(Auth::user()->account->customFieldsOption('client2_filter')) {
$query->addSelect('clients.custom_value2');
}
$this->applyFilters($query, ENTITY_CLIENT);
$this->applyFilters($query, ENTITY_CLIENT);
*/
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('clients.id_number', 'like', '%'.$filter.'%')
->orWhere('client_contacts.first_name', 'like', '%'.$filter.'%')
->orWhere('client_contacts.last_name', 'like', '%'.$filter.'%')
->orWhere('client_contacts.email', 'like', '%'.$filter.'%');
});
if ($filter) {
$query->where(function ($query) use ($filter) {
$query->where('clients.name', 'like', '%'.$filter.'%')
->orWhere('clients.id_number', 'like', '%'.$filter.'%')
->orWhere('client_contacts.first_name', 'like', '%'.$filter.'%')
->orWhere('client_contacts.last_name', 'like', '%'.$filter.'%')
->orWhere('client_contacts.email', 'like', '%'.$filter.'%');
});
/*
if(Auth::user()->account->customFieldsOption('client1_filter')) {
$query->orWhere('clients.custom_value1', 'like' , '%'.$filter.'%');
}
if(Auth::user()->account->customFieldsOption('client1_filter')) {
$query->orWhere('clients.custom_value1', 'like' , '%'.$filter.'%');
}
if(Auth::user()->account->customFieldsOption('client2_filter')) {
$query->orWhere('clients.custom_value2', 'like' , '%'.$filter.'%');
}
if(Auth::user()->account->customFieldsOption('client2_filter')) {
$query->orWhere('clients.custom_value2', 'like' , '%'.$filter.'%');
}
*/
}
}
if ($userId) {
$query->where('clients.user_id', '=', $userId);
}
if ($userId) {
$query->where('clients.user_id', '=', $userId);
}
return $query;
}
/**
* Returns the action dropdown menu
*
* @param $data Std Class of client datatable rows
* @return object Rendered action column items
*/
private function buildActionColumn($data) : object
{
//if(auth()->user()->is_admin())
//todo permissions are only mocked here, when user permissions have been implemented this needs to be refactored.
$permissions = [
'view_client',
'edit_client',
'create_task',
'create_invoice',
'create_payment',
'create_credit',
'create_expense'
];
$requested_actions = [
'view_client_client_id',
'edit_client_client_id',
'create_task_client_id',
'create_invoice_client_id',
'create_payment_client_id',
'create_credit_client_id',
'create_expense_client_id'
];
$is_admin = false;
$actions = $this->filterActions($requested_actions, $permissions, $is_admin);
$data->map(function ($row) use ($actions) {
$updated_actions = $actions->map(function ($action) use($row){
$action['url'] = route($action['route'], [$action['key'] => $this->encodePrimaryKey($row->id)]);
return $action;
});
$row->actions = $updated_actions;
return $row;
});
return $data;
}
return $query;
}
}

View File

@ -6,33 +6,6 @@ namespace App\Datatables;
class EntityDatatable
{
/**
* Returns the columns to be displayed and their key/values
* @return array Columns and key/value option pairs
*
* To be used to show/hide columns
*/
public function columns()
{
}
/**
* Display options for the ajax request
* @return array url, type, data
*/
public function ajax()
{
}
/**
* Builds the datatable
* @return DataTable returns a DataTable instance
*/
public function build()
{
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Datatables;
use Illuminate\Support\Collection;
trait MakesActionMenu
{
/**
* Returns all possible datatable actions
*
* @return Collection collection instance of action items
*/
public function actions() :Collection
{
return collect([
['action' => 'view_client_client_id', 'permission' => 'view_client', 'route' => 'clients.show', 'key' => 'client_id', 'name' => trans('texts.view')],
['action' => 'edit_client_client_id', 'permission' => 'edit_client', 'route' => 'clients.edit', 'key' => 'client_id', 'name' => trans('texts.edit')],
['action' => 'create_task_client_id', 'permission' => 'create_task', 'route' => 'tasks.create', 'key' => 'client_id', 'name' => trans('texts.new_task')],
['action' => 'create_invoice_client_id', 'permission' => 'create_invoice', 'route' => 'invoices.create', 'key' => 'client_id', 'name' => trans('texts.new_invoice')],
['action' => 'enter_payment_client_id', 'permission' => 'create_payment', 'route' => 'payments.create', 'key' => 'client_id', 'name' => trans('texts.enter_payment')],
['action' => 'enter_credit_client_id', 'permission' => 'create_credit', 'route' => 'credits.create', 'key' => 'client_id', 'name' => trans('texts.enter_credit')],
['action' => 'enter_expense_client_id', 'permission' => 'create_expense', 'route' => 'expenses.create', 'key' => 'client_id', 'name' => trans('texts.enter_expense')]
]);
}
/**
* Checks the user permissions against the collection and returns
* a Collection of available actions\.
*
* @param Collection $actions collection of possible actions
* @param bool $is_admin boolean defining if user is an administrator
* @return Collection collection of filtered actions
*/
private function checkPermissions(Collection $actions, array $permissions, bool $is_admin) :Collection
{
if($is_admin === TRUE)
return $actions;
return $actions->whereIn('permission', $permissions);
}
/**
* Filters the main actions collection down to the requested
* actions for this menu
*
* @param array $actions Array of actions requested
* @param array $permissions Array of user permissions
* @param bool $is_admin Boolean is_admin
* @return Collection collection of filtered actions available to the user
*/
public function filterActions(array $actions, array $permissions, bool $is_admin) :Collection
{
return $this->checkPermissions($this->actions()->whereIn('action', $actions), $permissions, $is_admin);
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Filters;
use Illuminate\Database\Eloquent\Builder;
class ClientFilters extends QueryFilters
{
/**
* Filters by due_date
*
* @param string $due_date
* @return Builder
*/
public function balance($balance)
{
$parts = $this->split($balance);
return $this->builder->where('balance', $parts->operator, $parts->value);
}
/**
* Filter by popularity.
*
//* @param string $order
//* @return Builder
public function popular($order = 'desc')
{
return $this->builder->orderBy('views', $order);
}
/**
* Filter by difficulty.
*
* @param string $level
* @return Builder
public function difficulty($level)
{
return $this->builder->where('difficulty', $level);
}
/**
* Filter by length.
*
* @param string $order
* @return Builder
public function length($order = 'asc')
{
return $this->builder->orderBy('length', $order);
}
*/
}

View File

@ -0,0 +1,110 @@
<?php
namespace App\Filters;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
abstract class QueryFilters
{
/**
* The request object.
*
* @var Request
*/
protected $request;
/**
* The builder instance.
*
* @var Builder
*/
protected $builder;
/**
* Create a new QueryFilters instance.
*
* @param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* Apply the filters to the builder.
*
* @param Builder $builder
* @return Builder
*/
public function apply(Builder $builder)
{
$this->builder = $builder;
foreach ($this->filters() as $name => $value) {
if (! method_exists($this, $name)) {
continue;
}
if (strlen($value)) {
$this->$name($value);
} else {
$this->$name();
}
}
return $this->builder;
}
/**
* Get all request filters data.
*
* @return array
*/
public function filters()
{
return $this->request->all();
}
/**
* Explodes the value by delimiter
*
* @param string $value
* @return array
*/
public function split($value) : stdClass
{
$exploded_array = explode(":", $value);
$parts = new stdClass;
$parts->value = $exploded_array[0];
$parts->operator = $this->operatorConvertor($exploded_array[1]);
return $parts;
}
private function operatorConvertor(string $operator) : string
{
switch ($operator) {
case 'lt':
return '<';
break;
case 'gt':
return '>';
break;
case 'lte':
return '<=';
break;
case 'gte':
return '>=';
break;
case 'eq':
return '=';
break;
default:
return '=';
break;
}
}
}

View File

@ -27,16 +27,19 @@ class ClientController extends Controller
protected $clientRepo;
public function __construct(ClientRepository $clientRepo)
protected $clientDatatable;
public function __construct(ClientRepository $clientRepo, ClientDatatable $clientDatatable)
{
$this->clientRepo = $clientRepo;
$this->clientDatatable = $clientDatatable;
}
public function index()
{
if(request('page'))
return ClientDatatable::query(request(), $this->getCurrentCompanyId());
return $this->clientDatatable->query(request(), $this->getCurrentCompanyId());
return view('client.vue_list');
/*

View File

@ -23,6 +23,7 @@ class DashboardController extends Controller
*/
public function index()
{
// dd(json_decode(auth()->user()->permissions(),true));
return view('dashboard.index');
}

View File

@ -13,6 +13,11 @@ class CompanyUser extends BaseModel
public function user()
{
return $this->hasOne(User::class);
return $this->hasOne(User::class)->withPivot('permissions');
}
public function company()
{
return $this->hasOne(Company::class)->withPivot('permissions');
}
}

View File

@ -2,9 +2,9 @@
namespace App\Models;
use App\Models\Traits\SetsUserSessionAttributes;
use App\Models\Traits\UserTrait;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\UserSessionAttributes;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
@ -17,7 +17,8 @@ class User extends Authenticatable implements MustVerifyEmail
use SoftDeletes;
use PresentableTrait;
use MakesHash;
use UserSessionAttributes;
protected $guard = 'user';
protected $dates = ['deleted_at'];
@ -54,11 +55,30 @@ class User extends Authenticatable implements MustVerifyEmail
'slack_webhook_url',
];
public function companies()
{
return $this->belongsToMany(Company::class);
return $this->belongsToMany(Company::class)->withPivot('permissions');
}
public function company()
{
return $this->companies()->where('company_id', $this->getCurrentCompanyId())->first();
}
public function permissions()
{
$permissions = json_decode($this->company()->pivot->permissions);
if (! $permissions)
return [];
return $permissions;
}
public function is_admin()
{
return $this->company()->pivot->is_admin;
}
public function contacts()
@ -66,9 +86,22 @@ class User extends Authenticatable implements MustVerifyEmail
return $this->hasMany(Contact::class);
}
public function owns($entity)
public function owns($entity) : bool
{
return ! empty($entity->user_id) && $entity->user_id == $this->id;
}
public function permissionsFlat()
{
return collect($this->permissions())->flatten();
}
public function permissionsMap()
{
$keys = array_values((array) $this->permissions());
$values = array_fill(0, count($keys), true);
return array_combine($keys, $values);
}
}

View File

@ -37,10 +37,21 @@ class UsersTableSeeder extends Seeder
'confirmation_code' => $this->createDbHash(config('database.default'))
]);
$userPermissions = collect([
'view_invoice',
'view_client',
'edit_client',
'edit_invoice',
'create_invoice',
'create_client'
]);
$user->companies()->attach($company->id, [
'account_id' => $account->id,
'is_owner' => 1,
'is_admin' => 1,
'permissions' => $userPermissions->toJson(),
'is_locked' => 0,
]);

View File

@ -1190,6 +1190,45 @@ Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/***/ }),
/***/ "./node_modules/babel-loader/lib/index.js?{\"cacheDirectory\":true,\"presets\":[[\"env\",{\"modules\":false,\"targets\":{\"browsers\":[\"> 2%\"],\"uglify\":true}}]],\"plugins\":[\"transform-object-rest-spread\",[\"transform-runtime\",{\"polyfill\":false,\"helpers\":false}]]}!./node_modules/vue-loader/lib/selector.js?type=script&index=0!./resources/js/src/components/client/ClientActions.vue":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
//
//
//
//
//
//
//
//
//
//
//
//
//
//
/* harmony default export */ __webpack_exports__["default"] = ({
props: {
rowData: {
type: Object,
required: true
},
rowIndex: {
type: Number
}
},
methods: {
itemAction: function itemAction(action, data, index) {
console.log('custom-actions: ' + action, data.name, index);
}
}
});
/***/ }),
/***/ "./node_modules/babel-loader/lib/index.js?{\"cacheDirectory\":true,\"presets\":[[\"env\",{\"modules\":false,\"targets\":{\"browsers\":[\"> 2%\"],\"uglify\":true}}]],\"plugins\":[\"transform-object-rest-spread\",[\"transform-runtime\",{\"polyfill\":false,\"helpers\":false}]]}!./node_modules/vue-loader/lib/selector.js?type=script&index=0!./resources/js/src/components/util/VuetablePaginationBootstrap.vue":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
@ -2694,6 +2733,21 @@ exports.push([module.i, "\n.form-inline > * {\n margin:5px 10px;\n}\n", ""]);
// exports
/***/ }),
/***/ "./node_modules/css-loader/index.js!./node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-d9a40d24\",\"scoped\":false,\"hasInlineConfig\":true}!./node_modules/vue-loader/lib/selector.js?type=styles&index=0!./resources/js/src/components/client/ClientActions.vue":
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__("./node_modules/css-loader/lib/css-base.js")(false);
// imports
// module
exports.push([module.i, "\n.custom-actions button.ui.button {\n padding: 8px 8px;\n}\n.custom-actions button.ui.button > i.icon {\n margin: auto !important;\n}\n", ""]);
// exports
/***/ }),
/***/ "./node_modules/css-loader/lib/css-base.js":
@ -3249,6 +3303,7 @@ var VuetablePagination_vue_1 = __importDefault(__webpack_require__("./node_modul
var VuetablePaginationInfo_vue_1 = __importDefault(__webpack_require__("./node_modules/vuetable-2/src/components/VuetablePaginationInfo.vue"));
var vue_1 = __importDefault(__webpack_require__("./node_modules/vue/dist/vue.common.js"));
var vue_events_1 = __importDefault(__webpack_require__("./node_modules/vue-events/dist/index.js"));
var VuetableCss_1 = __importDefault(__webpack_require__("./resources/js/src/components/util/VuetableCss.ts"));
vue_1.default.use(vue_events_1.default);
exports.default = {
components: {
@ -3258,6 +3313,7 @@ exports.default = {
},
data: function () {
return {
css: VuetableCss_1.default,
sortOrder: [
{
field: 'name',
@ -3304,31 +3360,14 @@ exports.default = {
name: 'balance',
sortField: 'balance',
dataClass: 'center aligned'
}
],
css: {
table: {
tableClass: 'table table-striped table-bordered table-hovered',
loadingClass: 'loading',
ascendingIcon: 'glyphicon glyphicon-chevron-up',
descendingIcon: 'glyphicon glyphicon-chevron-down',
handleIcon: 'glyphicon glyphicon-menu-hamburger',
},
pagination: {
infoClass: 'pull-left',
wrapperClass: 'vuetable-pagination pull-right',
activeClass: 'btn-primary',
disabledClass: 'disabled',
pageClass: 'btn btn-border',
linkClass: 'btn btn-border',
icons: {
first: '',
prev: '',
next: '',
last: '',
},
{
name: '__component:client-actions',
title: '',
titleClass: 'center aligned',
dataClass: 'center aligned'
}
}
]
};
},
//props: ['list'],
@ -4795,6 +4834,7 @@ var render = function() {
"per-page": 20,
"sort-order": _vm.sortOrder,
"append-params": _vm.moreParams,
css: _vm.css.table,
"pagination-path": ""
},
on: { "vuetable:pagination-data": _vm.onPaginationData }
@ -5060,6 +5100,74 @@ if (false) {
/***/ }),
/***/ "./node_modules/vue-loader/lib/template-compiler/index.js?{\"id\":\"data-v-d9a40d24\",\"hasScoped\":false,\"buble\":{\"transforms\":{}}}!./node_modules/vue-loader/lib/selector.js?type=template&index=0!./resources/js/src/components/client/ClientActions.vue":
/***/ (function(module, exports, __webpack_require__) {
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("div", { staticClass: "dropdown" }, [
_c(
"button",
{
staticClass: "btn btn-secondary dropdown-toggle",
attrs: {
type: "button",
id: "dropdownMenu",
"data-toggle": "dropdown",
"aria-haspopup": "true",
"aria-expanded": "false"
}
},
[_vm._v("\n\tSelect\n\t")]
),
_vm._v(" "),
_c(
"div",
{
staticClass: "dropdown-menu",
attrs: { "aria-labelledby": "dropdownMenu" }
},
[
_vm._l(_vm.rowData.actions, function(action) {
return _c(
"a",
{ staticClass: "dropdown-item", attrs: { href: action.url } },
[_vm._v(_vm._s(action.name))]
)
}),
_vm._v(" "),
_c(
"a",
{
staticClass: "dropdown-item",
attrs: { href: "#" },
on: {
click: function($event) {
_vm.itemAction("view-item", _vm.rowData, _vm.rowIndex)
}
}
},
[_vm._v("One more item")]
)
],
2
)
])
}
var staticRenderFns = []
render._withStripped = true
module.exports = { render: render, staticRenderFns: staticRenderFns }
if (false) {
module.hot.accept()
if (module.hot.data) {
require("vue-hot-reload-api") .rerender("data-v-d9a40d24", module.exports)
}
}
/***/ }),
/***/ "./node_modules/vue-style-loader/index.js!./node_modules/css-loader/index.js!./node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-15965e3b\",\"scoped\":true,\"hasInlineConfig\":true}!./node_modules/vue-loader/lib/selector.js?type=styles&index=0!./node_modules/vuetable-2/src/components/Vuetable.vue":
/***/ (function(module, exports, __webpack_require__) {
@ -5141,6 +5249,33 @@ if(false) {
/***/ }),
/***/ "./node_modules/vue-style-loader/index.js!./node_modules/css-loader/index.js!./node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-d9a40d24\",\"scoped\":false,\"hasInlineConfig\":true}!./node_modules/vue-loader/lib/selector.js?type=styles&index=0!./resources/js/src/components/client/ClientActions.vue":
/***/ (function(module, exports, __webpack_require__) {
// style-loader: Adds some css to the DOM by adding a <style> tag
// load the styles
var content = __webpack_require__("./node_modules/css-loader/index.js!./node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-d9a40d24\",\"scoped\":false,\"hasInlineConfig\":true}!./node_modules/vue-loader/lib/selector.js?type=styles&index=0!./resources/js/src/components/client/ClientActions.vue");
if(typeof content === 'string') content = [[module.i, content, '']];
if(content.locals) module.exports = content.locals;
// add the styles to the DOM
var update = __webpack_require__("./node_modules/vue-style-loader/lib/addStylesClient.js")("c5093156", content, false, {});
// Hot Module Replacement
if(false) {
// When the styles change, update the <style> tags
if(!content.locals) {
module.hot.accept("!!../../../../../node_modules/css-loader/index.js!../../../../../node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-d9a40d24\",\"scoped\":false,\"hasInlineConfig\":true}!../../../../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./ClientActions.vue", function() {
var newContent = require("!!../../../../../node_modules/css-loader/index.js!../../../../../node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-d9a40d24\",\"scoped\":false,\"hasInlineConfig\":true}!../../../../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./ClientActions.vue");
if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
update(newContent);
});
}
// When the module is disposed, remove the <style> tags
module.hot.dispose(function() { update(); });
}
/***/ }),
/***/ "./node_modules/vue-style-loader/lib/addStylesClient.js":
/***/ (function(module, exports, __webpack_require__) {
@ -18192,6 +18327,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
//import * as Vue from 'vue';
var vue_1 = __importDefault(__webpack_require__("./node_modules/vue/dist/vue.common.js"));
vue_1.default.component('client-list', __webpack_require__("./resources/js/src/components/client/ClientList.vue"));
vue_1.default.component('client-actions', __webpack_require__("./resources/js/src/components/client/ClientActions.vue"));
vue_1.default.component('vuetable', __webpack_require__("./node_modules/vuetable-2/src/components/Vuetable.vue"));
vue_1.default.component('vuetable-pagination', __webpack_require__("./node_modules/vuetable-2/src/components/VuetablePagination.vue"));
vue_1.default.component('vuetable-pagination-bootstrap', __webpack_require__("./resources/js/src/components/util/VuetablePaginationBootstrap.vue"));
@ -18203,6 +18339,58 @@ window.onload = function () {
};
/***/ }),
/***/ "./resources/js/src/components/client/ClientActions.vue":
/***/ (function(module, exports, __webpack_require__) {
var disposed = false
function injectStyle (ssrContext) {
if (disposed) return
__webpack_require__("./node_modules/vue-style-loader/index.js!./node_modules/css-loader/index.js!./node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-d9a40d24\",\"scoped\":false,\"hasInlineConfig\":true}!./node_modules/vue-loader/lib/selector.js?type=styles&index=0!./resources/js/src/components/client/ClientActions.vue")
}
var normalizeComponent = __webpack_require__("./node_modules/vue-loader/lib/component-normalizer.js")
/* script */
var __vue_script__ = __webpack_require__("./node_modules/babel-loader/lib/index.js?{\"cacheDirectory\":true,\"presets\":[[\"env\",{\"modules\":false,\"targets\":{\"browsers\":[\"> 2%\"],\"uglify\":true}}]],\"plugins\":[\"transform-object-rest-spread\",[\"transform-runtime\",{\"polyfill\":false,\"helpers\":false}]]}!./node_modules/vue-loader/lib/selector.js?type=script&index=0!./resources/js/src/components/client/ClientActions.vue")
/* template */
var __vue_template__ = __webpack_require__("./node_modules/vue-loader/lib/template-compiler/index.js?{\"id\":\"data-v-d9a40d24\",\"hasScoped\":false,\"buble\":{\"transforms\":{}}}!./node_modules/vue-loader/lib/selector.js?type=template&index=0!./resources/js/src/components/client/ClientActions.vue")
/* template functional */
var __vue_template_functional__ = false
/* styles */
var __vue_styles__ = injectStyle
/* scopeId */
var __vue_scopeId__ = null
/* moduleIdentifier (server only) */
var __vue_module_identifier__ = null
var Component = normalizeComponent(
__vue_script__,
__vue_template__,
__vue_template_functional__,
__vue_styles__,
__vue_scopeId__,
__vue_module_identifier__
)
Component.options.__file = "resources/js/src/components/client/ClientActions.vue"
/* hot reload */
if (false) {(function () {
var hotAPI = require("vue-hot-reload-api")
hotAPI.install(require("vue"), false)
if (!hotAPI.compatible) return
module.hot.accept()
if (!module.hot.data) {
hotAPI.createRecord("data-v-d9a40d24", Component.options)
} else {
hotAPI.reload("data-v-d9a40d24", Component.options)
}
module.hot.dispose(function (data) {
disposed = true
})
})()}
module.exports = Component.exports
/***/ }),
/***/ "./resources/js/src/components/client/ClientList.vue":
@ -18255,6 +18443,39 @@ if (false) {(function () {
module.exports = Component.exports
/***/ }),
/***/ "./resources/js/src/components/util/VuetableCss.ts":
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
table: {
tableClass: 'table table-bordered table-hover',
loadingClass: 'loading',
ascendingIcon: 'fa fa-angle-double-up',
descendingIcon: 'fa fa-angle-double-down',
handleIcon: 'glyphicon glyphicon-menu-hamburger',
},
pagination: {
infoClass: 'pull-left',
wrapperClass: 'vuetable-pagination pull-right',
activeClass: 'btn-primary',
disabledClass: 'disabled',
pageClass: 'btn btn-border',
linkClass: 'btn btn-border',
icons: {
first: '',
prev: '',
next: '',
last: '',
},
}
};
/***/ }),
/***/ "./resources/js/src/components/util/VuetableFilterBar.vue":

View File

@ -1190,6 +1190,45 @@ Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/***/ }),
/***/ "./node_modules/babel-loader/lib/index.js?{\"cacheDirectory\":true,\"presets\":[[\"env\",{\"modules\":false,\"targets\":{\"browsers\":[\"> 2%\"],\"uglify\":true}}]],\"plugins\":[\"transform-object-rest-spread\",[\"transform-runtime\",{\"polyfill\":false,\"helpers\":false}]]}!./node_modules/vue-loader/lib/selector.js?type=script&index=0!./resources/js/src/components/client/ClientActions.vue":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
//
//
//
//
//
//
//
//
//
//
//
//
//
//
/* harmony default export */ __webpack_exports__["default"] = ({
props: {
rowData: {
type: Object,
required: true
},
rowIndex: {
type: Number
}
},
methods: {
itemAction: function itemAction(action, data, index) {
console.log('custom-actions: ' + action, data.name, index);
}
}
});
/***/ }),
/***/ "./node_modules/babel-loader/lib/index.js?{\"cacheDirectory\":true,\"presets\":[[\"env\",{\"modules\":false,\"targets\":{\"browsers\":[\"> 2%\"],\"uglify\":true}}]],\"plugins\":[\"transform-object-rest-spread\",[\"transform-runtime\",{\"polyfill\":false,\"helpers\":false}]]}!./node_modules/vue-loader/lib/selector.js?type=script&index=0!./resources/js/src/components/util/VuetablePaginationBootstrap.vue":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
@ -2694,6 +2733,21 @@ exports.push([module.i, "\n.form-inline > * {\n margin:5px 10px;\n}\n", ""]);
// exports
/***/ }),
/***/ "./node_modules/css-loader/index.js!./node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-d9a40d24\",\"scoped\":false,\"hasInlineConfig\":true}!./node_modules/vue-loader/lib/selector.js?type=styles&index=0!./resources/js/src/components/client/ClientActions.vue":
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__("./node_modules/css-loader/lib/css-base.js")(false);
// imports
// module
exports.push([module.i, "\n.custom-actions button.ui.button {\n padding: 8px 8px;\n}\n.custom-actions button.ui.button > i.icon {\n margin: auto !important;\n}\n", ""]);
// exports
/***/ }),
/***/ "./node_modules/css-loader/lib/css-base.js":
@ -3249,6 +3303,7 @@ var VuetablePagination_vue_1 = __importDefault(__webpack_require__("./node_modul
var VuetablePaginationInfo_vue_1 = __importDefault(__webpack_require__("./node_modules/vuetable-2/src/components/VuetablePaginationInfo.vue"));
var vue_1 = __importDefault(__webpack_require__("./node_modules/vue/dist/vue.common.js"));
var vue_events_1 = __importDefault(__webpack_require__("./node_modules/vue-events/dist/index.js"));
var VuetableCss_1 = __importDefault(__webpack_require__("./resources/js/src/components/util/VuetableCss.ts"));
vue_1.default.use(vue_events_1.default);
exports.default = {
components: {
@ -3258,6 +3313,7 @@ exports.default = {
},
data: function () {
return {
css: VuetableCss_1.default,
sortOrder: [
{
field: 'name',
@ -3304,31 +3360,14 @@ exports.default = {
name: 'balance',
sortField: 'balance',
dataClass: 'center aligned'
}
],
css: {
table: {
tableClass: 'table table-striped table-bordered table-hovered',
loadingClass: 'loading',
ascendingIcon: 'glyphicon glyphicon-chevron-up',
descendingIcon: 'glyphicon glyphicon-chevron-down',
handleIcon: 'glyphicon glyphicon-menu-hamburger',
},
pagination: {
infoClass: 'pull-left',
wrapperClass: 'vuetable-pagination pull-right',
activeClass: 'btn-primary',
disabledClass: 'disabled',
pageClass: 'btn btn-border',
linkClass: 'btn btn-border',
icons: {
first: '',
prev: '',
next: '',
last: '',
},
{
name: '__component:client-actions',
title: '',
titleClass: 'center aligned',
dataClass: 'center aligned'
}
}
]
};
},
//props: ['list'],
@ -4795,6 +4834,7 @@ var render = function() {
"per-page": 20,
"sort-order": _vm.sortOrder,
"append-params": _vm.moreParams,
css: _vm.css.table,
"pagination-path": ""
},
on: { "vuetable:pagination-data": _vm.onPaginationData }
@ -5060,6 +5100,74 @@ if (false) {
/***/ }),
/***/ "./node_modules/vue-loader/lib/template-compiler/index.js?{\"id\":\"data-v-d9a40d24\",\"hasScoped\":false,\"buble\":{\"transforms\":{}}}!./node_modules/vue-loader/lib/selector.js?type=template&index=0!./resources/js/src/components/client/ClientActions.vue":
/***/ (function(module, exports, __webpack_require__) {
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("div", { staticClass: "dropdown" }, [
_c(
"button",
{
staticClass: "btn btn-secondary dropdown-toggle",
attrs: {
type: "button",
id: "dropdownMenu",
"data-toggle": "dropdown",
"aria-haspopup": "true",
"aria-expanded": "false"
}
},
[_vm._v("\n\tSelect\n\t")]
),
_vm._v(" "),
_c(
"div",
{
staticClass: "dropdown-menu",
attrs: { "aria-labelledby": "dropdownMenu" }
},
[
_vm._l(_vm.rowData.actions, function(action) {
return _c(
"a",
{ staticClass: "dropdown-item", attrs: { href: action.url } },
[_vm._v(_vm._s(action.name))]
)
}),
_vm._v(" "),
_c(
"a",
{
staticClass: "dropdown-item",
attrs: { href: "#" },
on: {
click: function($event) {
_vm.itemAction("view-item", _vm.rowData, _vm.rowIndex)
}
}
},
[_vm._v("One more item")]
)
],
2
)
])
}
var staticRenderFns = []
render._withStripped = true
module.exports = { render: render, staticRenderFns: staticRenderFns }
if (false) {
module.hot.accept()
if (module.hot.data) {
require("vue-hot-reload-api") .rerender("data-v-d9a40d24", module.exports)
}
}
/***/ }),
/***/ "./node_modules/vue-style-loader/index.js!./node_modules/css-loader/index.js!./node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-15965e3b\",\"scoped\":true,\"hasInlineConfig\":true}!./node_modules/vue-loader/lib/selector.js?type=styles&index=0!./node_modules/vuetable-2/src/components/Vuetable.vue":
/***/ (function(module, exports, __webpack_require__) {
@ -5141,6 +5249,33 @@ if(false) {
/***/ }),
/***/ "./node_modules/vue-style-loader/index.js!./node_modules/css-loader/index.js!./node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-d9a40d24\",\"scoped\":false,\"hasInlineConfig\":true}!./node_modules/vue-loader/lib/selector.js?type=styles&index=0!./resources/js/src/components/client/ClientActions.vue":
/***/ (function(module, exports, __webpack_require__) {
// style-loader: Adds some css to the DOM by adding a <style> tag
// load the styles
var content = __webpack_require__("./node_modules/css-loader/index.js!./node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-d9a40d24\",\"scoped\":false,\"hasInlineConfig\":true}!./node_modules/vue-loader/lib/selector.js?type=styles&index=0!./resources/js/src/components/client/ClientActions.vue");
if(typeof content === 'string') content = [[module.i, content, '']];
if(content.locals) module.exports = content.locals;
// add the styles to the DOM
var update = __webpack_require__("./node_modules/vue-style-loader/lib/addStylesClient.js")("c5093156", content, false, {});
// Hot Module Replacement
if(false) {
// When the styles change, update the <style> tags
if(!content.locals) {
module.hot.accept("!!../../../../../node_modules/css-loader/index.js!../../../../../node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-d9a40d24\",\"scoped\":false,\"hasInlineConfig\":true}!../../../../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./ClientActions.vue", function() {
var newContent = require("!!../../../../../node_modules/css-loader/index.js!../../../../../node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-d9a40d24\",\"scoped\":false,\"hasInlineConfig\":true}!../../../../../node_modules/vue-loader/lib/selector.js?type=styles&index=0!./ClientActions.vue");
if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
update(newContent);
});
}
// When the module is disposed, remove the <style> tags
module.hot.dispose(function() { update(); });
}
/***/ }),
/***/ "./node_modules/vue-style-loader/lib/addStylesClient.js":
/***/ (function(module, exports, __webpack_require__) {
@ -18192,6 +18327,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
//import * as Vue from 'vue';
var vue_1 = __importDefault(__webpack_require__("./node_modules/vue/dist/vue.common.js"));
vue_1.default.component('client-list', __webpack_require__("./resources/js/src/components/client/ClientList.vue"));
vue_1.default.component('client-actions', __webpack_require__("./resources/js/src/components/client/ClientActions.vue"));
vue_1.default.component('vuetable', __webpack_require__("./node_modules/vuetable-2/src/components/Vuetable.vue"));
vue_1.default.component('vuetable-pagination', __webpack_require__("./node_modules/vuetable-2/src/components/VuetablePagination.vue"));
vue_1.default.component('vuetable-pagination-bootstrap', __webpack_require__("./resources/js/src/components/util/VuetablePaginationBootstrap.vue"));
@ -18203,6 +18339,58 @@ window.onload = function () {
};
/***/ }),
/***/ "./resources/js/src/components/client/ClientActions.vue":
/***/ (function(module, exports, __webpack_require__) {
var disposed = false
function injectStyle (ssrContext) {
if (disposed) return
__webpack_require__("./node_modules/vue-style-loader/index.js!./node_modules/css-loader/index.js!./node_modules/vue-loader/lib/style-compiler/index.js?{\"vue\":true,\"id\":\"data-v-d9a40d24\",\"scoped\":false,\"hasInlineConfig\":true}!./node_modules/vue-loader/lib/selector.js?type=styles&index=0!./resources/js/src/components/client/ClientActions.vue")
}
var normalizeComponent = __webpack_require__("./node_modules/vue-loader/lib/component-normalizer.js")
/* script */
var __vue_script__ = __webpack_require__("./node_modules/babel-loader/lib/index.js?{\"cacheDirectory\":true,\"presets\":[[\"env\",{\"modules\":false,\"targets\":{\"browsers\":[\"> 2%\"],\"uglify\":true}}]],\"plugins\":[\"transform-object-rest-spread\",[\"transform-runtime\",{\"polyfill\":false,\"helpers\":false}]]}!./node_modules/vue-loader/lib/selector.js?type=script&index=0!./resources/js/src/components/client/ClientActions.vue")
/* template */
var __vue_template__ = __webpack_require__("./node_modules/vue-loader/lib/template-compiler/index.js?{\"id\":\"data-v-d9a40d24\",\"hasScoped\":false,\"buble\":{\"transforms\":{}}}!./node_modules/vue-loader/lib/selector.js?type=template&index=0!./resources/js/src/components/client/ClientActions.vue")
/* template functional */
var __vue_template_functional__ = false
/* styles */
var __vue_styles__ = injectStyle
/* scopeId */
var __vue_scopeId__ = null
/* moduleIdentifier (server only) */
var __vue_module_identifier__ = null
var Component = normalizeComponent(
__vue_script__,
__vue_template__,
__vue_template_functional__,
__vue_styles__,
__vue_scopeId__,
__vue_module_identifier__
)
Component.options.__file = "resources/js/src/components/client/ClientActions.vue"
/* hot reload */
if (false) {(function () {
var hotAPI = require("vue-hot-reload-api")
hotAPI.install(require("vue"), false)
if (!hotAPI.compatible) return
module.hot.accept()
if (!module.hot.data) {
hotAPI.createRecord("data-v-d9a40d24", Component.options)
} else {
hotAPI.reload("data-v-d9a40d24", Component.options)
}
module.hot.dispose(function (data) {
disposed = true
})
})()}
module.exports = Component.exports
/***/ }),
/***/ "./resources/js/src/components/client/ClientList.vue":
@ -18255,6 +18443,39 @@ if (false) {(function () {
module.exports = Component.exports
/***/ }),
/***/ "./resources/js/src/components/util/VuetableCss.ts":
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
table: {
tableClass: 'table table-bordered table-hover',
loadingClass: 'loading',
ascendingIcon: 'fa fa-angle-double-up',
descendingIcon: 'fa fa-angle-double-down',
handleIcon: 'glyphicon glyphicon-menu-hamburger',
},
pagination: {
infoClass: 'pull-left',
wrapperClass: 'vuetable-pagination pull-right',
activeClass: 'btn-primary',
disabledClass: 'disabled',
pageClass: 'btn btn-border',
linkClass: 'btn btn-border',
icons: {
first: '',
prev: '',
next: '',
last: '',
},
}
};
/***/ }),
/***/ "./resources/js/src/components/util/VuetableFilterBar.vue":

View File

@ -3,6 +3,7 @@ import Vue from 'vue';
import axios from 'axios';
Vue.component('client-list', require('../components/client/ClientList.vue'));
Vue.component('client-actions', require('../components/client/ClientActions.vue'));
Vue.component('vuetable', require('vuetable-2/src/components/Vuetable'));
Vue.component('vuetable-pagination', require('vuetable-2/src/components/VuetablePagination'));
Vue.component('vuetable-pagination-bootstrap', require('../components/util/VuetablePaginationBootstrap'));

View File

@ -0,0 +1,41 @@
<template>
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Select
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenu">
<a class="dropdown-item" :href="action.url" v-for="action in rowData.actions">{{ action.name }}</a>
<a class="dropdown-item" href="#" @click="itemAction('view-item', rowData, rowIndex)">One more item</a>
</div>
</div>
</template>
<script>
export default {
props: {
rowData: {
type: Object,
required: true
},
rowIndex: {
type: Number
}
},
methods: {
itemAction (action, data, index) {
console.log('custom-actions: ' + action, data.name, index)
}
}
}
</script>
<style>
.custom-actions button.ui.button {
padding: 8px 8px;
}
.custom-actions button.ui.button > i.icon {
margin: auto !important;
}
</style>

View File

@ -9,6 +9,7 @@
:per-page="20"
:sort-order="sortOrder"
:append-params="moreParams"
:css="css.table"
pagination-path=""
@vuetable:pagination-data="onPaginationData"></vuetable>
@ -33,6 +34,7 @@ import VuetablePagination from 'vuetable-2/src/components/VuetablePagination.vue
import VuetablePaginationInfo from 'vuetable-2/src/components/VuetablePaginationInfo.vue'
import Vue from 'vue'
import VueEvents from 'vue-events'
import VuetableCss from '../util/VuetableCss'
Vue.use(VueEvents)
@ -44,6 +46,7 @@ export default {
},
data () {
return {
css: VuetableCss,
sortOrder: [
{
field: 'name',
@ -90,31 +93,14 @@ export default {
name: 'balance',
sortField: 'balance',
dataClass: 'center aligned'
}
],
css: {
table: {
tableClass: 'table table-striped table-bordered table-hovered',
loadingClass: 'loading',
ascendingIcon: 'glyphicon glyphicon-chevron-up',
descendingIcon: 'glyphicon glyphicon-chevron-down',
handleIcon: 'glyphicon glyphicon-menu-hamburger',
},
pagination: {
infoClass: 'pull-left',
wrapperClass: 'vuetable-pagination pull-right',
activeClass: 'btn-primary',
disabledClass: 'disabled',
pageClass: 'btn btn-border',
linkClass: 'btn btn-border',
icons: {
first: '',
prev: '',
next: '',
last: '',
},
{
name: '__component:client-actions', // <----
title: '',
titleClass: 'center aligned',
dataClass: 'center aligned'
}
}
]
}
},
//props: ['list'],

View File

@ -0,0 +1,23 @@
export default {
table: {
tableClass: 'table table-bordered table-hover',
loadingClass: 'loading',
ascendingIcon: 'fa fa-angle-double-up',
descendingIcon: 'fa fa-angle-double-down',
handleIcon: 'glyphicon glyphicon-menu-hamburger',
},
pagination: {
infoClass: 'pull-left',
wrapperClass: 'vuetable-pagination pull-right',
activeClass: 'btn-primary',
disabledClass: 'disabled',
pageClass: 'btn btn-border',
linkClass: 'btn btn-border',
icons: {
first: '',
prev: '',
next: '',
last: '',
},
}
}

View File

@ -41,10 +41,14 @@ Route::group(['middleware' => ['auth:user', 'db']], function () {
Route::resource('dashboard', 'DashboardController'); // name = (dashboard. index / create / show / update / destroy / edit
Route::get('logout', 'Auth\LoginController@logout')->name('user.logout');
Route::resource('invoices', 'InvoiceController'); // name = (invoices. index / create / show / update / destroy / edit
Route::resource('clients', 'ClientController'); // name = (clients. index / create / show / update / destroy / edit
Route::resource('user', 'UserProfileController'); // name = (clients. index / create / show / update / destroy / edit
Route::get('settings', 'SettingsController@index')->name('user.settings');
Route::resource('invoices', 'InvoiceController'); // name = (invoices. index / create / show / update / destroy / edit
Route::resource('clients', 'ClientController'); // name = (clients. index / create / show / update / destroy / edit
Route::resource('tasks', 'TaskController'); // name = (tasks. index / create / show / update / destroy / edit
Route::resource('payments', 'PaymentController'); // name = (payments. index / create / show / update / destroy / edit
Route::resource('credits', 'CreditController'); // name = (credits. index / create / show / update / destroy / edit
Route::resource('expenses', 'ExpenseController'); // name = (expenses. index / create / show / update / destroy / edit
Route::resource('user', 'UserProfileController'); // name = (clients. index / create / show / update / destroy / edit
Route::get('settings', 'SettingsController@index')->name('user.settings');

View File

@ -5,10 +5,10 @@ namespace Tests\Feature;
use App\Models\Account;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Session;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class LoginTest extends TestCase
{

View File

@ -0,0 +1,138 @@
<?php
namespace Tests\Unit;
use Tests\TestCase;
/**
* @test
* @covers App\Utils\NumberHelper
*/
class CompareCollectionTest extends TestCase
{
public function setUp()
{
parent::setUp();
$this->map = collect([
['action' => 'view_client_client_id', 'permission' => 'view_client', 'route' => 'clients.show', 'key' => 'client_id', 'name' => trans('texts.view')],
['action' => 'edit_client_client_id', 'permission' => 'edit_client', 'route' => 'clients.edit', 'key' => 'client_id', 'name' => trans('texts.edit')],
['action' => 'create_task_client_id', 'permission' => 'create_task', 'route' => 'task.create', 'key' => 'client_id', 'name' => trans('texts.new_task')],
['action' => 'create_invoice_client_id', 'permission' => 'create_invoice', 'route' => 'invoice.create', 'key' => 'client_id', 'name' => trans('texts.new_invoice')],
['action' => 'enter_payment_client_id', 'permission' => 'create_payment', 'route' => 'payment.create', 'key' => 'client_id', 'name' => trans('texts.enter_payment')],
['action' => 'enter_credit_client_id', 'permission' => 'create_credit', 'route' => 'credit.create', 'key' => 'client_id', 'name' => trans('texts.enter_credit')],
['action' => 'enter_expense_client_id', 'permission' => 'create_expense', 'route' => 'expense.create', 'key' => 'client_id', 'name' => trans('texts.enter_expense')]
]);
$this->view_permission = ['view_client'];
$this->edit_permission = ['view_client', 'edit_client'];
$this->is_admin = true;
$this->is_not_admin = false;
}
public function testCompareResultOfComparison()
{
$this->assertEquals(7, $this->map->count());
}
public function testViewPermission()
{
$this->assertEquals(1, $this->checkPermissions($this->view_permission, $this->is_not_admin)->count());
}
public function testViewAndEditPermission()
{
$this->assertEquals(2, $this->checkPermissions($this->edit_permission, $this->is_not_admin)->count());
}
public function testAdminPermissions()
{
$this->assertEquals(7, $this->checkPermissions($this->view_permission, $this->is_admin)->count());
}
public function testActionViewClientFilter()
{
$actions = [
'view_client_client_id'
];
$this->assertEquals(1, $this->map->whereIn('action', $actions)->count());
}
public function testNoActionClientFilter()
{
$actions = [
''
];
$this->assertEquals(0, $this->map->whereIn('action', $actions)->count());
}
public function testActionsAndPermissionsFilter()
{
$actions = [
'view_client_client_id'
];
$this->filterActions($actions);
$this->assertEquals(1, $this->checkPermissions($this->view_permission, $this->is_not_admin)->count());
}
public function testActionAndPermissionsFilterFailure()
{
$actions = [
'edit_client_client_id'
];
$data = $this->filterActions($actions);
$this->assertEquals(0, $data->whereIn('permission', $this->view_permission)->count());
}
public function testEditActionAndPermissionsFilter()
{
$actions = [
'edit_client_client_id'
];
$data = $this->filterActions($actions);
$this->assertEquals(1, $data->whereIn('permission', $this->edit_permission)->count());
}
public function checkPermissions($permission, $is_admin)
{
if($is_admin === TRUE)
return $this->map;
return $this->map->whereIn('permission', $permission);
}
public function filterActions($actions)
{
return $this->map->whereIn('action', $actions);
}
}