search-string config check columns
This commit is contained in:
parent
d4a3f9c1a3
commit
2b0255b487
@ -4,6 +4,7 @@ namespace App\View\Components;
|
|||||||
|
|
||||||
use Illuminate\View\Component;
|
use Illuminate\View\Component;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||||
|
|
||||||
class SearchString extends Component
|
class SearchString extends Component
|
||||||
{
|
{
|
||||||
@ -44,7 +45,11 @@ class SearchString extends Component
|
|||||||
if (!is_array($options)) {
|
if (!is_array($options)) {
|
||||||
$column = $options;
|
$column = $options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$this->isFilter($column, $options)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$this->filters[] = [
|
$this->filters[] = [
|
||||||
'key' => $this->getFilterKey($column, $options),
|
'key' => $this->getFilterKey($column, $options),
|
||||||
'value' => $this->getFilterName($column),
|
'value' => $this->getFilterName($column),
|
||||||
@ -58,6 +63,17 @@ class SearchString extends Component
|
|||||||
return view('components.search-string');
|
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)
|
protected function getFilterKey($column, $options)
|
||||||
{
|
{
|
||||||
if (isset($options['relationship'])) {
|
if (isset($options['relationship'])) {
|
||||||
@ -71,20 +87,22 @@ class SearchString extends Component
|
|||||||
{
|
{
|
||||||
if (strpos($column, '_id') !== false) {
|
if (strpos($column, '_id') !== false) {
|
||||||
$column = str_replace('_id', '', $column);
|
$column = str_replace('_id', '', $column);
|
||||||
|
} else if (strpos($column, '_code') !== false) {
|
||||||
|
$column = str_replace('_code', '', $column);
|
||||||
}
|
}
|
||||||
|
|
||||||
$plural = Str::plural($column, 2);
|
$plural = Str::plural($column, 2);
|
||||||
|
|
||||||
if (trans_choice('general.' . $plural, 1) !== 'general.' . $plural) {
|
if (trans_choice('general.' . $plural, 1) !== 'general.' . $plural) {
|
||||||
return trans_choice('general.' . $plural, 1);
|
return trans_choice('general.' . $plural, 1);
|
||||||
} elseif (trans_choice('search_string.colmuns.' . $plural, 1) !== 'search_string.colmuns.' . $plural) {
|
} elseif (trans_choice('search_string.columns.' . $plural, 1) !== 'search_string.columns.' . $plural) {
|
||||||
return trans_choice('search_string.colmuns.' . $plural, 1);
|
return trans_choice('search_string.columns.' . $plural, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$name = trans('general.' . $column);
|
$name = trans('general.' . $column);
|
||||||
|
|
||||||
if ($name == 'general.' . $column) {
|
if ($name == 'general.' . $column) {
|
||||||
$name = trans('search_string.colmuns.' . $column);
|
$name = trans('search_string.columns.' . $column);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $name;
|
return $name;
|
||||||
@ -98,6 +116,10 @@ class SearchString extends Component
|
|||||||
$type = 'boolean';
|
$type = 'boolean';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($options['date'])) {
|
||||||
|
$type = 'date';
|
||||||
|
}
|
||||||
|
|
||||||
return $type;
|
return $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +127,7 @@ class SearchString extends Component
|
|||||||
{
|
{
|
||||||
$url = '';
|
$url = '';
|
||||||
|
|
||||||
if (isset($options['boolean'])) {
|
if (isset($options['boolean']) || isset($options['date'])) {
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +150,11 @@ class SearchString extends Component
|
|||||||
|
|
||||||
$plural = Str::plural($column, 2);
|
$plural = Str::plural($column, 2);
|
||||||
|
|
||||||
$url = route($url . $plural . '.index');
|
try {
|
||||||
|
$url = route($url . $plural . '.index');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$url = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
|
@ -69,7 +69,9 @@ return [
|
|||||||
'number' => ['searchable' => true],
|
'number' => ['searchable' => true],
|
||||||
'bank_name' => ['searchable' => true],
|
'bank_name' => ['searchable' => true],
|
||||||
'bank_address' => ['searchable' => true],
|
'bank_address' => ['searchable' => true],
|
||||||
'currency' => ['relationship' => true],
|
'currency_code' => [
|
||||||
|
'route' => 'currencies.index'
|
||||||
|
],
|
||||||
'enabled' => ['boolean' => true],
|
'enabled' => ['boolean' => true],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -87,16 +89,24 @@ return [
|
|||||||
App\Models\Banking\Transaction::class => [
|
App\Models\Banking\Transaction::class => [
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'type',
|
'type',
|
||||||
'account_id',
|
'account_id' => [
|
||||||
|
'route' => 'accounts.index'
|
||||||
|
],
|
||||||
'paid_at' => ['date' => true],
|
'paid_at' => ['date' => true],
|
||||||
'amount',
|
'amount',
|
||||||
'currency_code',
|
'currency_code' => [
|
||||||
|
'route' => 'currencies.index'
|
||||||
|
],
|
||||||
'document_id',
|
'document_id',
|
||||||
'contact_id',
|
'contact_id' => [
|
||||||
|
'route' => 'customers.index'
|
||||||
|
],
|
||||||
'description' => ['searchable' => true],
|
'description' => ['searchable' => true],
|
||||||
'payment_method',
|
'payment_method',
|
||||||
'reference',
|
'reference',
|
||||||
'category_id',
|
'category_id' => [
|
||||||
|
'route' => 'categories.index'
|
||||||
|
],
|
||||||
'parent_id',
|
'parent_id',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -123,6 +133,8 @@ return [
|
|||||||
'category_id' => [
|
'category_id' => [
|
||||||
'route' => ['categories.index', 'search=type:item']
|
'route' => ['categories.index', 'search=type:item']
|
||||||
],
|
],
|
||||||
|
'sales_price',
|
||||||
|
'purchase_price',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -135,7 +147,9 @@ return [
|
|||||||
'phone' => ['searchable' => true],
|
'phone' => ['searchable' => true],
|
||||||
'address' => ['searchable' => true],
|
'address' => ['searchable' => true],
|
||||||
'website' => ['searchable' => true],
|
'website' => ['searchable' => true],
|
||||||
'currency_code',
|
'currency_code' => [
|
||||||
|
'route' => 'currencies.index'
|
||||||
|
],
|
||||||
'reference',
|
'reference',
|
||||||
'user_id',
|
'user_id',
|
||||||
'enabled' => ['boolean' => true],
|
'enabled' => ['boolean' => true],
|
||||||
@ -150,14 +164,20 @@ return [
|
|||||||
'billed_at' => ['date' => true],
|
'billed_at' => ['date' => true],
|
||||||
'due_at' => ['date' => true],
|
'due_at' => ['date' => true],
|
||||||
'amount',
|
'amount',
|
||||||
'currency_code',
|
'currency_code' => [
|
||||||
'contact_id',
|
'route' => 'currencies.index'
|
||||||
|
],
|
||||||
|
'contact_id' => [
|
||||||
|
'route' => 'vendors.index'
|
||||||
|
],
|
||||||
'contact_name' => ['searchable' => true],
|
'contact_name' => ['searchable' => true],
|
||||||
'contact_email' => ['searchable' => true],
|
'contact_email' => ['searchable' => true],
|
||||||
'contact_tax_number',
|
'contact_tax_number',
|
||||||
'contact_phone' => ['searchable' => true],
|
'contact_phone' => ['searchable' => true],
|
||||||
'contact_address' => ['searchable' => true],
|
'contact_address' => ['searchable' => true],
|
||||||
'category_id',
|
'category_id' => [
|
||||||
|
'route' => 'categories.index'
|
||||||
|
],
|
||||||
'parent_id',
|
'parent_id',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -170,14 +190,20 @@ return [
|
|||||||
'invoiced_at' => ['date' => true],
|
'invoiced_at' => ['date' => true],
|
||||||
'due_at' => ['date' => true],
|
'due_at' => ['date' => true],
|
||||||
'amount',
|
'amount',
|
||||||
'currency_code',
|
'currency_code' => [
|
||||||
'contact_id',
|
'route' => 'currencies.index'
|
||||||
|
],
|
||||||
|
'contact_id' => [
|
||||||
|
'route' => 'customer.index'
|
||||||
|
],
|
||||||
'contact_name' => ['searchable' => true],
|
'contact_name' => ['searchable' => true],
|
||||||
'contact_email' => ['searchable' => true],
|
'contact_email' => ['searchable' => true],
|
||||||
'contact_tax_number',
|
'contact_tax_number',
|
||||||
'contact_phone' => ['searchable' => true],
|
'contact_phone' => ['searchable' => true],
|
||||||
'contact_address' => ['searchable' => true],
|
'contact_address' => ['searchable' => true],
|
||||||
'category_id',
|
'category_id' => [
|
||||||
|
'route' => 'categories.index'
|
||||||
|
],
|
||||||
'parent_id',
|
'parent_id',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
<div class="dropdown input-full">
|
<div class="dropdown input-full">
|
||||||
<input
|
<input
|
||||||
|
v-if="!show_date"
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@ -27,6 +28,20 @@
|
|||||||
@keyup.enter="onInputConfirm"
|
@keyup.enter="onInputConfirm"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
<button type="button" class="btn btn-link clear" @click="onSearchAndFilterClear">
|
<button type="button" class="btn btn-link clear" @click="onSearchAndFilterClear">
|
||||||
<i class="el-tag__close el-icon-close"></i>
|
<i class="el-tag__close el-icon-close"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -62,9 +77,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import flatPicker from "vue-flatpickr-component";
|
||||||
|
import "flatpickr/dist/flatpickr.css";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'akaunting-search',
|
name: 'akaunting-search',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
flatPicker
|
||||||
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -106,6 +128,8 @@ export default {
|
|||||||
default: () => [],
|
default: () => [],
|
||||||
description: 'List of filters'
|
description: 'List of filters'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dateConfig: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
model: {
|
model: {
|
||||||
@ -131,6 +155,7 @@ export default {
|
|||||||
selected_values: [],
|
selected_values: [],
|
||||||
values: [],
|
values: [],
|
||||||
current_value: null,
|
current_value: null,
|
||||||
|
show_date: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -140,13 +165,59 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.visible[this.filter_last_step] = true;
|
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.$nextTick(() => {
|
||||||
this.$refs['input-search-field-' + this._uid].focus();
|
this.$refs['input-search-field-' + this._uid].focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Focus :' + this.filter_last_step);
|
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) {
|
onInput(evt) {
|
||||||
@ -158,24 +229,26 @@ export default {
|
|||||||
option_url += '?search=' + this.search;
|
option_url += '?search=' + this.search;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.axios.get(option_url)
|
if (option_url) {
|
||||||
.then(response => {
|
window.axios.get(option_url)
|
||||||
this.values = [];
|
.then(response => {
|
||||||
|
this.values = [];
|
||||||
|
|
||||||
let data = response.data.data;
|
let data = response.data.data;
|
||||||
|
|
||||||
data.forEach(function (item) {
|
data.forEach(function (item) {
|
||||||
this.values.push({
|
this.values.push({
|
||||||
key: item.id,
|
key: item.id,
|
||||||
value: item.name
|
value: item.name
|
||||||
});
|
});
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
this.option_values[value] = this.values;
|
this.option_values[value] = this.values;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('input', evt.target.value);
|
this.$emit('input', evt.target.value);
|
||||||
},
|
},
|
||||||
@ -202,7 +275,6 @@ export default {
|
|||||||
|
|
||||||
args += this.selected_options[index].key + ':' + this.selected_values[index].key + ' ';
|
args += this.selected_options[index].key + ':' + this.selected_values[index].key + ' ';
|
||||||
|
|
||||||
|
|
||||||
serach_string[path][this.selected_options[index].key] = {
|
serach_string[path][this.selected_options[index].key] = {
|
||||||
'key': this.selected_values[index].key,
|
'key': this.selected_values[index].key,
|
||||||
'value': this.selected_values[index].value
|
'value': this.selected_values[index].value
|
||||||
@ -218,7 +290,7 @@ export default {
|
|||||||
this.current_value = value;
|
this.current_value = value;
|
||||||
|
|
||||||
let option = false;
|
let option = false;
|
||||||
let option_url = url;
|
let option_url = false;
|
||||||
|
|
||||||
for (let i = 0; i < this.filter_list.length; i++) {
|
for (let i = 0; i < this.filter_list.length; i++) {
|
||||||
if (this.filter_list[i].key == value) {
|
if (this.filter_list[i].key == value) {
|
||||||
@ -249,14 +321,14 @@ export default {
|
|||||||
|
|
||||||
this.search = '';
|
this.search = '';
|
||||||
|
|
||||||
if (!this.option_values[value]) {
|
if (!this.option_values[value] && option_url) {
|
||||||
window.axios.get(option_url)
|
window.axios.get(option_url)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
let data = response.data.data;
|
let data = response.data.data;
|
||||||
|
|
||||||
data.forEach(function (item) {
|
data.forEach(function (item) {
|
||||||
this.values.push({
|
this.values.push({
|
||||||
key: item.id,
|
key: (item.code) ? item.code : item.id,
|
||||||
value: (item.title) ? item.title : (item.display_name) ? item.display_name : item.name
|
value: (item.title) ? item.title : (item.display_name) ? item.display_name : item.name
|
||||||
});
|
});
|
||||||
}, this);
|
}, this);
|
||||||
@ -267,7 +339,7 @@ export default {
|
|||||||
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.values = this.option_values[value];
|
this.values = (this.option_values[value]) ? this.option_values[value] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@ -288,15 +360,29 @@ export default {
|
|||||||
|
|
||||||
this.$emit('change', this.filtered);
|
this.$emit('change', this.filtered);
|
||||||
|
|
||||||
this.$nextTick(() => {
|
if (this.selected_options[this.filter_index].type != 'date') {
|
||||||
this.$refs['input-search-field-' + this._uid].focus();
|
this.$nextTick(() => {
|
||||||
});
|
this.$refs['input-search-field-' + this._uid].focus();
|
||||||
|
});
|
||||||
|
|
||||||
this.visible = {
|
this.visible = {
|
||||||
options: false,
|
options: false,
|
||||||
operator: false,
|
operator: false,
|
||||||
values: true,
|
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';
|
this.filter_last_step = 'values';
|
||||||
},
|
},
|
||||||
@ -338,6 +424,8 @@ export default {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.show_date = false;
|
||||||
|
|
||||||
this.filter_last_step = 'options';
|
this.filter_last_step = 'options';
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -586,4 +674,8 @@ export default {
|
|||||||
-webkit-box-shadow: none !important;
|
-webkit-box-shadow: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.searh-field .form-control.datepicker.flatpickr-input {
|
||||||
|
padding: inherit !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
9
resources/lang/en-GB/search_string.php
Normal file
9
resources/lang/en-GB/search_string.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'columns' => [
|
||||||
|
'last_logged_in_at' => 'Last Login',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
@ -11,18 +11,12 @@
|
|||||||
@section('content')
|
@section('content')
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header border-bottom-0" :class="[{'bg-gradient-primary': bulk_action.show}]">
|
<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">
|
<div class="align-items-center" v-if="!bulk_action.show">
|
||||||
<x-search-string model="App\Models\Auth\User" />
|
<x-search-string model="App\Models\Auth\User" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ Form::bulkActionRowGroup('general.users', $bulk_actions, ['group' => 'auth', 'type' => 'users']) }}
|
{{ Form::bulkActionRowGroup('general.users', $bulk_actions, ['group' => 'auth', 'type' => 'users']) }}
|
||||||
{!! Form::close() !!}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
|
@ -7,4 +7,16 @@
|
|||||||
no-matching-data-text="{{ trans('general.no_matching_data') }}"
|
no-matching-data-text="{{ trans('general.no_matching_data') }}"
|
||||||
value="{{ request()->get('search', null) }}"
|
value="{{ request()->get('search', null) }}"
|
||||||
:filters="{{ json_encode($filters) }}"
|
: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>
|
></akaunting-search>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user