Merge pull request #1657 from cuneytsenturk/search-filter

Search & Filter enhancement feature..
This commit is contained in:
Cüneyt Şentürk 2020-11-11 14:50:33 +03:00 committed by GitHub
commit 6dbf2a8d5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 992 additions and 259 deletions

View File

@ -6,9 +6,7 @@ use App\Abstracts\Http\Controller;
use App\Http\Requests\Common\BulkAction as Request;
use Illuminate\Support\Str;
class
BulkActions extends Controller
class BulkActions extends Controller
{
/**

View File

@ -0,0 +1,196 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\Support\Str;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
class SearchString extends Component
{
public $filters;
public $model;
/**
* Create a new component instance.
*
* @return void
*/
public function __construct($model)
{
$this->model = $model;
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\Contracts\View\View|string
*/
public function render()
{
$searc_string = config('search-string');
$this->filters = [];
if (!empty($searc_string[$this->model])) {
$columns = $searc_string[$this->model]['columns'];
foreach ($columns as $column => $options) {
// This column skip for filter
if (!empty($options['searchable'])) {
continue;
}
if (!is_array($options)) {
$column = $options;
}
if (!$this->isFilter($column, $options)) {
continue;
}
$this->filters[] = [
'key' => $this->getFilterKey($column, $options),
'value' => $this->getFilterName($column),
'type' => $this->getFilterType($options),
'url' => $this->getFilterUrl($column, $options),
'values' => $this->getFilterValues($column, $options),
];
}
}
return view('components.search-string');
}
protected function isFilter($column, $options)
{
$filter = true;
if (empty($this->getFilterUrl($column, $options)) && (!isset($options['date']) && !isset($options['boolean']))) {
$filter = false;
}
return $filter;
}
protected function getFilterKey($column, $options)
{
if (isset($options['relationship'])) {
$column .= '.id';
}
return $column;
}
protected function getFilterName($column)
{
if (strpos($column, '_id') !== false) {
$column = str_replace('_id', '', $column);
} else if (strpos($column, '_code') !== false) {
$column = str_replace('_code', '', $column);
}
$plural = Str::plural($column, 2);
if (trans_choice('general.' . $plural, 1) !== 'general.' . $plural) {
return trans_choice('general.' . $plural, 1);
} elseif (trans_choice('search_string.columns.' . $plural, 1) !== 'search_string.columns.' . $plural) {
return trans_choice('search_string.columns.' . $plural, 1);
}
$name = trans('general.' . $column);
if ($name == 'general.' . $column) {
$name = trans('search_string.columns.' . $column);
}
return $name;
}
protected function getFilterType($options)
{
$type = 'select';
if (isset($options['boolean'])) {
$type = 'boolean';
}
if (isset($options['date'])) {
$type = 'date';
}
return $type;
}
protected function getFilterUrl($column, $options)
{
$url = '';
if (isset($options['boolean']) || isset($options['date'])) {
return $url;
}
if (!empty($options['route'])) {
if (is_array($options['route'])) {
$url = route($options['route'][0], $options['route'][1]);
} else {
$url = route($options['route']);
}
} else {
if (strpos($this->model, 'Modules') !== false) {
$module_class = explode('\\', $this->model);
$url .= Str::slug($module_class[1], '-') . '::';
}
if (strpos($column, '_id') !== false) {
$column = str_replace('_id', '', $column);
}
$plural = Str::plural($column, 2);
try {
$url = route($url . $plural . '.index');
} catch (\Exception $e) {
$url = '';
}
}
return $url;
}
protected function getFilterValues($column, $options)
{
$values = [];
if (isset($options['boolean'])) {
$values = [
[
'key' => 0,
'value' => trans('general.no'),
],
[
'key' => 1,
'value' => trans('general.yes'),
],
];
} else if ($search = request()->get('search', false)) {
$fields = explode(' ', $search);
foreach ($fields as $field) {
if (strpos($field, ':') === false) {
continue;
}
$filters = explode(':', $field);
if ($filters[0] != $column) {
continue;
}
}
}
return $values;
}
}

View File

@ -69,7 +69,9 @@ return [
'number' => ['searchable' => true],
'bank_name' => ['searchable' => true],
'bank_address' => ['searchable' => true],
'currency' => ['relationship' => true],
'currency_code' => [
'route' => 'currencies.index'
],
'enabled' => ['boolean' => true],
],
],
@ -87,16 +89,24 @@ return [
App\Models\Banking\Transaction::class => [
'columns' => [
'type',
'account_id',
'account_id' => [
'route' => 'accounts.index'
],
'paid_at' => ['date' => true],
'amount',
'currency_code',
'currency_code' => [
'route' => 'currencies.index'
],
'document_id',
'contact_id',
'contact_id' => [
'route' => 'customers.index'
],
'description' => ['searchable' => true],
'payment_method',
'reference',
'category_id',
'category_id' => [
'route' => 'categories.index'
],
'parent_id',
],
],
@ -120,8 +130,10 @@ return [
'name' => ['searchable' => true],
'description' => ['searchable' => true],
'enabled' => ['boolean' => true],
'category_id' => ['key' => 'category_id'],
'sale_price',
'category_id' => [
'route' => ['categories.index', 'search=type:item']
],
'sales_price',
'purchase_price',
],
],
@ -135,7 +147,9 @@ return [
'phone' => ['searchable' => true],
'address' => ['searchable' => true],
'website' => ['searchable' => true],
'currency_code',
'currency_code' => [
'route' => 'currencies.index'
],
'reference',
'user_id',
'enabled' => ['boolean' => true],
@ -150,14 +164,20 @@ return [
'billed_at' => ['date' => true],
'due_at' => ['date' => true],
'amount',
'currency_code',
'contact_id',
'currency_code' => [
'route' => 'currencies.index'
],
'contact_id' => [
'route' => 'vendors.index'
],
'contact_name' => ['searchable' => true],
'contact_email' => ['searchable' => true],
'contact_tax_number',
'contact_phone' => ['searchable' => true],
'contact_address' => ['searchable' => true],
'category_id',
'category_id' => [
'route' => 'categories.index'
],
'parent_id',
],
],
@ -170,14 +190,20 @@ return [
'invoiced_at' => ['date' => true],
'due_at' => ['date' => true],
'amount',
'currency_code',
'contact_id',
'currency_code' => [
'route' => 'currencies.index'
],
'contact_id' => [
'route' => 'customer.index'
],
'contact_name' => ['searchable' => true],
'contact_email' => ['searchable' => true],
'contact_tax_number',
'contact_phone' => ['searchable' => true],
'contact_address' => ['searchable' => true],
'category_id',
'category_id' => [
'route' => 'categories.index'
],
'parent_id',
],
],

View File

@ -846,3 +846,9 @@ table .align-items-center td span.badge {
border: 1px solid #3c3f72;
}
/*--lightbox Finish--*/
/*-- Search string & BulkAction Start --*/
#app > .card > .card-header {
min-height: 88px;
}
/*-- Search string & BulkAction Finish --*/

View File

@ -7,19 +7,26 @@
*/
;(function (factory) {
var registeredInModuleLoader = false;
if (typeof define === 'function' && define.amd) {
define(factory);
registeredInModuleLoader = true;
}
if (typeof exports === 'object') {
module.exports = factory();
registeredInModuleLoader = true;
}
if (!registeredInModuleLoader) {
var OldCookies = window.Cookies;
var api = window.Cookies = factory();
api.noConflict = function () {
window.Cookies = OldCookies;
return api;
};
}
@ -27,24 +34,27 @@
function extend () {
var i = 0;
var result = {};
for (; i < arguments.length; i++) {
var attributes = arguments[ i ];
for (var key in attributes) {
result[key] = attributes[key];
}
}
return result;
}
function init (converter) {
function api (key, value, attributes) {
var result;
if (typeof document === 'undefined') {
return;
}
// Write
if (arguments.length > 1) {
attributes = extend({
path: '/'
@ -52,7 +62,9 @@
if (typeof attributes.expires === 'number') {
var expires = new Date();
expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
attributes.expires = expires;
}
@ -61,6 +73,7 @@
try {
result = JSON.stringify(value);
if (/^[\{\[]/.test(result)) {
value = result;
}
@ -83,17 +96,20 @@
if (!attributes[attributeName]) {
continue;
}
stringifiedAttributes += '; ' + attributeName;
if (attributes[attributeName] === true) {
continue;
}
stringifiedAttributes += '=' + attributes[attributeName];
}
return (document.cookie = key + '=' + value + stringifiedAttributes);
}
// Read
if (!key) {
result = {};
}
@ -115,6 +131,7 @@
try {
var name = parts[0].replace(rdecode, decodeURIComponent);
cookie = converter.read ?
converter.read(cookie, name) : converter(cookie, name) ||
cookie.replace(rdecode, decodeURIComponent);
@ -140,14 +157,17 @@
}
api.set = api;
api.get = function (key) {
return api.call(api, key);
};
api.getJSON = function () {
return api.apply({
json: true
}, [].slice.call(arguments));
};
api.defaults = {};
api.remove = function (key, attributes) {

View File

@ -1,170 +1,681 @@
<template>
<div :id="'search-field-' + _uid" class="searh-field tags-input__wrapper">
<div class="tags-group" v-for="(filter, index) in filtered" :index="index">
<span v-if="filter.option" class="el-tag el-tag--primary el-tag--small el-tag--light el-tag-option">
{{ filter.option }}
</span>
<el-select
:class="'pl-20 mr-40'"
v-model="real_model"
@input="change"
filterable
remote
reserve-keyword
<span v-if="filter.operator" class="el-tag el-tag--primary el-tag--small el-tag--light el-tag-operator">
{{ (filter.operator == '=') ? operatorIsText : operatorIsNotText }}
</span>
<span v-if="filter.value" class="el-tag el-tag--primary el-tag--small el-tag--light el-tag-value">
{{ filter.value }}
<i class="el-tag__close el-icon-close" @click="onFilterDelete(index)"></i>
</span>
</div>
<div class="dropdown input-full">
<input
v-if="!show_date"
type="text"
class="form-control"
:placeholder="placeholder"
:remote-method="remoteMethod"
:loading="loading">
<div v-if="loading" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty loading">
{{ loadingText }}
</p>
</div>
:ref="'input-search-field-' + _uid"
v-model="search"
@focus="onInputFocus"
@input="onInput"
@keyup.enter="onInputConfirm"
/>
<div v-else-if="options.length != 0" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty">
{{ noMatchingDataText }}
</p>
</div>
<flat-picker
v-else
@on-open="onInputFocus"
:config="dateConfig"
class="form-control datepicker"
:placeholder="placeholder"
:ref="'input-search-date-field-' + _uid"
v-model="search"
@focus="onInputFocus"
@input="onInputDateSelected"
@keyup.enter="onInputConfirm"
>
</flat-picker>
<div v-else-if="options.length == 0" slot="empty">
<p class="el-select-dropdown__empty">
{{ noDataText }}
</p>
</div>
<button type="button" class="btn btn-link clear" @click="onSearchAndFilterClear">
<i class="el-tag__close el-icon-close"></i>
</button>
<template v-if="icon" slot="prefix">
<span class="el-input__suffix-inner el-select-icon">
<i :class="'select-icon-position el-input__icon fa fa-' + icon"></i>
</span>
</template>
<div :id="'search-field-option-' + _uid" class="dropdown-menu" :class="[{'show': visible.options}]">
<li ref="" class="dropdown-item" v-for="option in filteredOptions" :data-value="option.key">
<button type="button" class="btn btn-link" @click="onOptionSelected(option.key)">{{ option.value }}</button>
</li>
<li ref="" v-if="search" class="dropdown-item">
<button type="button" class="btn btn-link" @click="onInputConfirm">{{ searchText }}</button>
</li>
</div>
<el-option v-if="!group" v-for="option in selectOptions"
:key="option.id"
:label="option.name"
:value="option.id">
</el-option>
<el-option-group
v-if="group"
v-for="(options, name) in selectOptions"
:key="name"
:label="name">
<el-option
v-for="(label, value) in options"
:key="value"
:label="label"
:value="value">
</el-option>
</el-option-group>
</el-select>
<div :id="'search-field-operator-' + _uid" class="dropdown-menu" :class="[{'show': visible.operator}]">
<li ref="" class="dropdown-item">
<button type="button" class="btn btn-link" @click="onOperatorSelected('=')">{{ operatorIsText }}<span class="btn-helptext d-none">is</span></button>
</li>
<li ref="" class="dropdown-item">
<button type="button" class="btn btn-link" @click="onOperatorSelected('!=')">{{ operatorIsNotText }}<span class="btn-helptext d-none">is not</span></button>
</li>
</div>
<div :id="'search-field-value-' + _uid" class="dropdown-menu" :class="[{'show': visible.values}]">
<li ref="" class="dropdown-item" v-for="(value) in filteredValues" :data-value="value.key">
<button type="button" class="btn btn-link" @click="onValueSelected(value.key)">{{ value.value }}</button>
</li>
<li ref="" class="dropdown-item" v-if="!filteredValues.length">
<button type="button" class="btn btn-link">{{ noMatchingDataText }}</button>
</li>
</div>
</div>
</div>
</template>
<style>
.el-select {
display: inline;
}
</style>
<script>
import { Select, Option, OptionGroup } from 'element-ui';
import flatPicker from "vue-flatpickr-component";
import "flatpickr/dist/flatpickr.css";
export default {
name: 'akaunting-select',
export default {
name: 'akaunting-search',
components: {
[Select.name]: Select,
[Option.name]: Option,
[OptionGroup.name]: OptionGroup,
},
components: {
flatPicker
},
props: {
name: {
type: String,
default: null,
description: "Selectbox attribute name"
},
placeholder: {
type: String,
default: '',
description: "Selectbox input placeholder text"
},
options: null,
value: {
type: String,
default: null,
description: "Selectbox selected value"
},
props: {
placeholder: {
type: String,
default: 'Search or filter results...',
description: 'Input placeholder'
},
searchText: {
type: String,
default: 'Search for this text',
description: 'Input placeholder'
},
operatorIsText: {
type: String,
default: 'is',
description: 'Operator is "="'
},
operatorIsNotText: {
type: String,
default: 'is not',
description: 'Operator is not "!="'
},
noDataText: {
type: String,
default: 'No Data',
description: "Selectbox empty options message"
},
noMatchingDataText: {
type: String,
default: 'No Matchign Data',
description: "Selectbox search option not found item message"
},
value: {
type: String,
default: null,
description: 'Search attribute value'
},
filters: {
type: Array,
default: () => [],
description: 'List of filters'
},
icon: {
type: String,
description: "Prepend icon (left)"
},
dateConfig: null,
},
group: {
type: Boolean,
default: false,
description: "Selectbox option group status"
},
model: {
prop: 'value',
event: 'change'
},
loadingText: {
type: String,
default: 'Loading...',
description: "Selectbox loading message"
},
noDataText: {
type: String,
default: 'No Data',
description: "Selectbox empty options message"
},
noMatchingDataText: {
type: String,
default: 'No Matchign Data',
description: "Selectbox search option not found item message"
},
data() {
return {
filter_list: this.filters, // set filters prop assing it.
search: '', // search cloumn model
filtered:[], // Show selected filters
filter_index: 0, // added filter count
filter_last_step: 'options', // last fitler step
visible: { // Which visible dropdown
options: false,
operator: false,
values: false,
},
remoteAction: {
type: String,
default: null,
description: "Selectbox remote action path"
},
},
option_values: [],
selected_options: [],
selected_values: [],
values: [],
current_value: null,
show_date: false,
};
},
data() {
return {
list: [],
selectOptions: this.options,
real_model: this.model,
loading: false,
methods: {
onInputFocus() {
if (!this.filter_list.length) {
return;
}
if (this.filter_last_step != 'values' || (this.filter_last_step == 'values' && this.selected_options[this.filter_index].type != 'date')) {
this.visible[this.filter_last_step] = true;
this.$nextTick(() => {
this.$refs['input-search-field-' + this._uid].focus();
});
} else {
this.show_date = true;
this.$nextTick(() => {
this.$refs['input-search-date-field-' + this._uid].focus();
});
}
console.log('Focus :' + this.filter_last_step);
},
onInputDateSelected(event) {
this.filtered[this.filter_index].value = event;
this.selected_values.push({
key: event,
value: event,
});
this.$emit('change', this.filtered);
this.show_date = false;
this.search = '';
this.$nextTick(() => {
this.$refs['input-search-field-' + this._uid].focus();
});
this.filter_index++;
if (this.filter_list.length) {
this.visible = {
options: true,
operator: false,
values: false,
};
} else {
this.visible = {
options: false,
operator: false,
values: false,
};
}
this.show_date = false;
this.filter_last_step = 'options';
},
onInput(evt) {
this.search = evt.target.value;
let option_url = this.selected_options[this.filter_index].url;
if (this.search) {
option_url += '?search=' + this.search;
}
if (option_url) {
window.axios.get(option_url)
.then(response => {
this.values = [];
let data = response.data.data;
data.forEach(function (item) {
this.values.push({
key: item.id,
value: item.name
});
}, this);
this.option_values[value] = this.values;
})
.catch(error => {
});
}
this.$emit('input', evt.target.value);
},
onInputConfirm() {
let path = window.location.href.replace(window.location.search, '');
let args = '';
if (this.search) {
args += '?search=' + this.search + ' ';
}
let now = new Date();
now.setTime(now.getTime() + 1 * 3600 * 1000);
let expires = now.toUTCString();
let serach_string = {};
serach_string[path] = {};
this.filtered.forEach(function (filter, index) {
if (!args) {
args += '?search=';
}
args += this.selected_options[index].key + ':' + this.selected_values[index].key + ' ';
serach_string[path][this.selected_options[index].key] = {
'key': this.selected_values[index].key,
'value': this.selected_values[index].value
};
}, this);
Cookies.set('search-string', serach_string, expires);
window.location = path + args;
},
onOptionSelected(value) {
this.current_value = value;
let option = false;
let option_url = false;
for (let i = 0; i < this.filter_list.length; i++) {
if (this.filter_list[i].key == value) {
option = this.filter_list[i].value;
if (typeof this.filter_list[i].url !== 'undefined' && this.filter_list[i].url) {
option_url = this.filter_list[i].url;
}
if (typeof this.filter_list[i].type !== 'undefined' && this.filter_list[i].type == 'boolean') {
this.option_values[value] = this.filter_list[i].values;
}
this.selected_options.push(this.filter_list[i]);
this.filter_list.splice(i, 1);
break;
}
}
// Set filtered select option
this.filtered.push({
option: option,
operator: false,
value: false
});
this.$emit('change', this.filtered);
this.search = '';
if (!this.option_values[value] && option_url) {
window.axios.get(option_url)
.then(response => {
let data = response.data.data;
data.forEach(function (item) {
this.values.push({
key: (item.code) ? item.code : item.id,
value: (item.title) ? item.title : (item.display_name) ? item.display_name : item.name
});
}, this);
this.option_values[value] = this.values;
})
.catch(error => {
});
} else {
this.values = (this.option_values[value]) ? this.option_values[value] : [];
}
this.$nextTick(() => {
this.$refs['input-search-field-' + this._uid].focus();
});
this.visible = {
options: false,
operator: true,
values: false,
};
this.filter_last_step = 'operator';
},
onOperatorSelected(value) {
this.filtered[this.filter_index].operator = value;
this.$emit('change', this.filtered);
if (this.selected_options[this.filter_index].type != 'date') {
this.$nextTick(() => {
this.$refs['input-search-field-' + this._uid].focus();
});
this.visible = {
options: false,
operator: false,
values: true,
};
} else {
this.show_date = true;
this.$nextTick(() => {
this.$refs['input-search-date-field-' + this._uid].focus();
});
this.visible = {
options: false,
operator: false,
values: false,
};
}
this.filter_last_step = 'values';
},
onValueSelected(value) {
let select_value = false;
for (let i = 0; i < this.values.length; i++) {
if (this.values[i].key == value) {
select_value = this.values[i].value;
this.selected_values.push(this.values[i]);
this.option_values[this.current_value].splice(i, 1);
break;
}
}
this.filtered[this.filter_index].value = select_value;
this.$emit('change', this.filtered);
this.$nextTick(() => {
this.$refs['input-search-field-' + this._uid].focus();
});
this.filter_index++;
if (this.filter_list.length) {
this.visible = {
options: true,
operator: false,
values: false,
};
} else {
this.visible = {
options: false,
operator: false,
values: false,
};
}
this.show_date = false;
this.filter_last_step = 'options';
},
onFilterDelete(index) {
this.filter_list.push(this.selected_options[index]);
this.filter_index--;
this.filtered.splice(index, 1);
this.selected_options.splice(index, 1);
},
onSearchAndFilterClear() {
this.filtered = [];
this.search = '';
Cookies.remove('search-string');
this.onInputConfirm();
},
closeIfClickedOutside(event) {
if (!document.getElementById('search-field-' + this._uid).contains(event.target) && event.target.className != 'btn btn-link') {
this.visible.options = false;
this.visible.operator = false;
this.visible.values = false;
document.removeEventListener('click', this.closeIfClickedOutside);
}
},
},
created() {
let path = window.location.href.replace(window.location.search, '');
let cookie = Cookies.get('search-string');
cookie = JSON.parse(cookie)[path];
if (this.value) {
let serach_string = this.value.split(' ');
serach_string.forEach(function (string) {
if (string.search(':') === -1) {
this.search = string;
} else {
let filter = string.split(':');
let option = '';
let value = '';
this.filter_list.forEach(function (_filter, i) {
if (_filter.key == filter[0]) {
option = _filter.value;
_filter.values.forEach(function (_value) {
if (_value.key == filter[1]) {
value = _value.value;
}
}, this);
if (!value && cookie[_filter.key]) {
value = cookie[_filter.key].value;
}
this.selected_options.push(this.filter_list[i]);
this.filter_list.splice(i, 1);
this.option_values[_filter.key] = _filter.values;
_filter.values.forEach(function (value, j) {
if (value.key == filter[1]) {
this.selected_values.push(value);
this.option_values[_filter.key].splice(j, 1);
}
}, this);
if (cookie[_filter.key]) {
this.selected_values.push(cookie[_filter.key]);
}
}
},
}, this);
mounted() {
this.real_model = this.value;
this.filtered.push({
option: option,
operator: '=',
value: value
});
this.$emit('interface', this.real_model);
},
methods: {
change() {
this.$emit('change', this.real_model);
this.$emit('interface', this.real_model);
this.selectOptions.forEach(item => {
if (item.id == this.real_model) {
this.$emit('label', item.name);
this.$emit('option', item);
return;
}
});
},
remoteMethod() {
},
},
watch: {
options: function (options) {
// update options
//this.selectOptions = options;
}
},
this.filter_index++;
}
}, this);
}
},
computed: {
filteredOptions() {
this.filter_list.sort(function (a, b) {
var nameA = a.value.toUpperCase(); // ignore upper and lowercase
var nameB = b.value.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
});
return this.filter_list.filter(option => {
return option.value.toLowerCase().includes(this.search.toLowerCase())
});
},
filteredValues() {
this.values.sort(function (a, b) {
var nameA = a.value.toUpperCase(); // ignore upper and lowercase
var nameB = b.value.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
});
return this.values.filter(value => {
return value.value.toLowerCase().includes(this.search.toLowerCase())
})
}
},
watch: {
visible: {
handler: function(newValue) {
if (newValue) {
document.addEventListener('click', this.closeIfClickedOutside);
}
},
deep: true
}
},
};
</script>
<style>
.searh-field {
border: 1px solid #dee2e6;
border-radius: 0.25rem;
}
.searh-field .tags-group {
display: contents;
}
.searh-field .el-tag.el-tag--primary {
background: #f6f9fc;
background-color: #f6f9fc;
border-color: #f6f9fc;
color: #8898aa;
margin-top: 7px;
}
.searh-field .tags-group:hover > span {
background:#cbd4de;
background-color: #cbd4de;
border-color: #cbd4de;
}
.searh-field .el-tag.el-tag--primary .el-tag__close.el-icon-close {
color: #8898aa;
}
.searh-field .el-tag-option {
border-radius: 0.25rem 0 0 0.25rem;
margin-left: 10px;
}
.searh-field .el-tag-operator {
border-radius: 0;
margin-left: -1px;
margin-right: -1px;
}
.searh-field .el-tag-value {
border-radius: 0 0.25rem 0.25rem 0;
margin-right: 10px;
}
.searh-field .el-select.input-new-tag {
width: 100%;
}
.searh-field .input-full {
width: 100%;
}
.searh-field .btn.btn-link {
width: inherit;
text-align: left;
display: flex;
margin: 0;
text-overflow: inherit;
text-align: left;
text-overflow: ellipsis;
padding: unset;
color: #212529;
}
.searh-field .btn.btn-link.clear {
position: absolute;
top: 0;
right: 0;
width: 45px;
height: 45px;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
color: #adb5bd;
opacity: 1;
}
.searh-field .btn.btn-link.clear:hover {
color: #8898aa;
}
.searh-field .btn-helptext {
margin-left: auto;
color: var(--gray);
}
.searh-field .btn:not(:disabled):not(.disabled):active:focus,
.searh-field .btn:not(:disabled):not(.disabled).active:focus {
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
.searh-field .form-control.datepicker.flatpickr-input {
padding: inherit !important;
}
</style>

View File

@ -26,7 +26,7 @@ const app = new Vue({
],
mounted() {
if (!this.form.permissions.length) {
if (typeof this.form.permissions !== 'undefined' && !this.form.permissions.length) {
this.form.permissions = [];
}
},

View File

@ -105,6 +105,7 @@ return [
'to' => 'To',
'print' => 'Print',
'search' => 'Search',
'search_text' => 'Search for this text',
'search_placeholder' => 'Type to search..',
'filter' => 'Filter',
'help' => 'Help',
@ -152,6 +153,8 @@ return [
'no_matching_data' => 'No matching data',
'clear_cache' => 'Clear Cache',
'go_to_dashboard' => 'Go to dashboard',
'is' => 'is',
'isnot' => 'is not',
'card' => [
'name' => 'Name on Card',
@ -181,6 +184,11 @@ return [
'no_file_selected' => 'No file selected...',
],
'placeholder' => [
'search' => 'Type to search..',
'search_and_filter' => 'Search or filter results..',
],
'date_range' => [
'today' => 'Today',
'yesterday' => 'Yesterday',

View File

@ -0,0 +1,9 @@
<?php
return [
'columns' => [
'last_logged_in_at' => 'Last Login',
],
];

View File

@ -18,10 +18,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Auth\Permission" />
</div>
{{ Form::bulkActionRowGroup('general.permissions', $bulk_actions, ['group' => 'auth', 'type' => 'permissions']) }}

View File

@ -18,10 +18,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Auth\Role" />
</div>
{{ Form::bulkActionRowGroup('general.roles', $bulk_actions, ['group' => 'auth', 'type' => 'roles']) }}

View File

@ -11,21 +11,12 @@
@section('content')
<div class="card">
<div class="card-header border-bottom-0" :class="[{'bg-gradient-primary': bulk_action.show}]">
{!! Form::open([
'method' => 'GET',
'route' => 'users.index',
'role' => 'form',
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Auth\User" />
</div>
{{ Form::bulkActionRowGroup('general.users', $bulk_actions, ['group' => 'auth', 'type' => 'users']) }}
{!! Form::close() !!}
</div>
<div class="table-responsive">

View File

@ -18,10 +18,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Banking\Account" />
</div>
{{ Form::bulkActionRowGroup('general.accounts', $bulk_actions, ['group' => 'banking', 'type' => 'accounts']) }}

View File

@ -9,7 +9,7 @@
@endsection
@section('content')
@if ($reconciliations->count())
@if ($reconciliations->count() || request()->get('search', false))
<div class="card">
<div class="card-header border-bottom-0" :class="[{'bg-gradient-primary': bulk_action.show}]">
{!! Form::open([
@ -19,10 +19,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Banking\Reconciliation" />
</div>
{{ Form::bulkActionRowGroup('general.reconciliations', $bulk_actions, ['group' => 'banking', 'type' => 'reconciliations']) }}

View File

@ -22,10 +22,7 @@
'role' => 'form',
'class' => 'mb-0'
]) !!}
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Banking\Transaction" />
{!! Form::close() !!}
</div>

View File

@ -11,7 +11,7 @@
@endsection
@section('content')
@if ($transfers->count())
@if ($transfers->count() || request()->get('search', false))
<div class="card">
<div class="card-header border-bottom-0" :class="[{'bg-gradient-primary': bulk_action.show}]">
{!! Form::open([
@ -21,10 +21,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Banking\Transfer" />
</div>
{{ Form::bulkActionRowGroup('general.transfers', $bulk_actions, ['group' => 'banking', 'type' => 'transfers']) }}

View File

@ -18,10 +18,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Common\Company" />
</div>
{{ Form::bulkActionRowGroup('general.companies', $bulk_actions, ['group' => 'common', 'type' => 'companies']) }}

View File

@ -18,10 +18,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Common\Dashboard" />
</div>
{{ Form::bulkActionRowGroup('general.dashboards', $bulk_actions, ['group' => 'common', 'type' => 'dashboards']) }}

View File

@ -11,7 +11,7 @@
@endsection
@section('content')
@if ($items->count())
@if ($items->count() || request()->get('search', false))
<div class="card">
<div class="card-header border-bottom-0" :class="[{'bg-gradient-primary': bulk_action.show}]">
{!! Form::open([
@ -21,10 +21,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Common\Item" />
</div>
{{ Form::bulkActionRowGroup('general.items', $bulk_actions, ['group' => 'common', 'type' => 'items']) }}

View File

@ -0,0 +1,22 @@
<akaunting-search
placeholder="{{ (!empty($filters)) ? trans('general.placeholder.search_and_filter') : trans('general.search_placeholder')}}"
search-text="{{ trans('general.search_text') }}"
operator-is-text="{{ trans('general.is') }}"
operator-is-not-text="{{ trans('general.isnot') }}"
no-data-text="{{ trans('general.no_data') }}"
no-matching-data-text="{{ trans('general.no_matching_data') }}"
value="{{ request()->get('search', null) }}"
:filters="{{ json_encode($filters) }}"
:date-config="{
allowInput: true,
altInput: true,
altFormat: 'Y-m-d',
dateFormat: 'Y-m-d',
@if (!empty($attributes['min-date']))
minDate: {{ $attributes['min-date'] }}
@endif
@if (!empty($attributes['max-date']))
maxDate: {{ $attributes['max-date'] }}
@endif
}"
></akaunting-search>

View File

@ -11,7 +11,7 @@
@endsection
@section('content')
@if ($bills->count())
@if ($bills->count() || request()->get('search', false))
<div class="card">
<div class="card-header border-bottom-0" :class="[{'bg-gradient-primary': bulk_action.show}]">
{!! Form::open([
@ -21,10 +21,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Purchase\Bill" />
</div>
{{ Form::bulkActionRowGroup('general.bills', $bulk_actions, ['group' => 'purchases', 'type' => 'bills']) }}

View File

@ -11,7 +11,7 @@
@endsection
@section('content')
@if ($payments->count())
@if ($payments->count() || request()->get('search', false))
<div class="card">
<div class="card-header border-bottom-0" :class="[{'bg-gradient-primary': bulk_action.show}]">
{!! Form::open([
@ -21,10 +21,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Banking\Transaction" />
</div>
{{ Form::bulkActionRowGroup('general.payments', $bulk_actions, ['group' => 'purchases', 'type' => 'payments']) }}

View File

@ -11,7 +11,7 @@
@endsection
@section('content')
@if ($vendors->count())
@if ($vendors->count() || request()->get('search', false))
<div class="card">
<div class="card-header border-bottom-0" :class="[{'bg-gradient-primary': bulk_action.show}]">
{!! Form::open([
@ -21,10 +21,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Common\Contact" />
</div>
{{ Form::bulkActionRowGroup('general.vendors', $bulk_actions, ['group' => 'purchases', 'type' => 'vendors']) }}

View File

@ -11,7 +11,7 @@
@endsection
@section('content')
@if ($customers->count())
@if ($customers->count() || request()->get('search', false))
<div class="card">
<div class="card-header border-bottom-0" :class="[{'bg-gradient-primary': bulk_action.show}]">
{!! Form::open([
@ -21,10 +21,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Common\Contact" />
</div>
{{ Form::bulkActionRowGroup('general.customers', $bulk_actions, ['group' => 'sales', 'type' => 'customers']) }}

View File

@ -11,7 +11,7 @@
@endsection
@section('content')
@if ($invoices->count())
@if ($invoices->count() || request()->get('search', false))
<div class="card">
<div class="card-header border-bottom-0" :class="[{'bg-gradient-primary': bulk_action.show}]">
{!! Form::open([
@ -21,10 +21,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Sale\Invoice" />
</div>
{{ Form::bulkActionRowGroup('general.invoices', $bulk_actions, ['group' => 'sales', 'type' => 'invoices']) }}

View File

@ -11,7 +11,7 @@
@endsection
@section('content')
@if ($revenues->count())
@if ($revenues->count() || request()->get('search', false))
<div class="card">
<div class="card-header border-bottom-0" :class="[{'bg-gradient-primary': bulk_action.show}]">
{!! Form::open([
@ -21,10 +21,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Banking\Transaction" />
</div>
{{ Form::bulkActionRowGroup('general.revenues', $bulk_actions, ['group' => 'sales', 'type' => 'revenues']) }}

View File

@ -18,10 +18,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Setting\Category" />
</div>
{{ Form::bulkActionRowGroup('general.categories', $bulk_actions, ['group' => 'settings', 'type' => 'categories']) }}

View File

@ -18,10 +18,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Setting\Currency" />
</div>
{{ Form::bulkActionRowGroup('general.currencies', $bulk_actions, ['group' => 'settings', 'type' => 'currencies']) }}

View File

@ -9,7 +9,7 @@
@endcan
@section('content')
@if ($taxes->count())
@if ($taxes->count() || request()->get('search', false))
<div class="card">
<div class="card-header border-bottom-0" :class="[{'bg-gradient-primary': bulk_action.show}]">
{!! Form::open([
@ -19,10 +19,7 @@
'class' => 'mb-0'
]) !!}
<div class="align-items-center" v-if="!bulk_action.show">
<akaunting-search
:placeholder="'{{ trans('general.search_placeholder') }}'"
:options="{{ json_encode([]) }}"
></akaunting-search>
<x-search-string model="App\Models\Setting\Tax" />
</div>
{{ Form::bulkActionRowGroup('general.taxes', $bulk_actions, ['group' => 'settings', 'type' => 'taxes']) }}