- improved "status" select field, #662
This commit is contained in:
@@ -34,12 +34,8 @@ class System extends Controller\AccessController {
|
||||
$mapData = (array)$postData['mapData'];
|
||||
$systemModel = null;
|
||||
|
||||
if( isset($systemData['statusId']) ){
|
||||
if( (int)$systemData['statusId'] <= 0){
|
||||
unset($systemData['statusId']);
|
||||
}else{
|
||||
$systemData['statusId'] = (int)$systemData['statusId'];
|
||||
}
|
||||
if( (int)$systemData['statusId'] <= 0 ){
|
||||
unset($systemData['statusId']);
|
||||
}
|
||||
|
||||
if( isset($systemData['id']) ){
|
||||
@@ -72,7 +68,7 @@ class System extends Controller\AccessController {
|
||||
|
||||
if( !is_null($systemModel) ){
|
||||
// set/update system custom data
|
||||
$systemModel->copyfrom($systemData, ['locked', 'rallyUpdated', 'position', 'description']);
|
||||
$systemModel->copyfrom($systemData, ['statusId', 'locked', 'rallyUpdated', 'position', 'description']);
|
||||
|
||||
if($systemModel->save($activeCharacter)){
|
||||
// get data from "fresh" model (e.g. some relational data has changed: "statusId")
|
||||
|
||||
@@ -74,6 +74,7 @@ class SystemModel extends AbstractMapTrackingModel {
|
||||
'on-delete' => 'CASCADE'
|
||||
]
|
||||
],
|
||||
'validate' => true,
|
||||
'activity-log' => true
|
||||
],
|
||||
'locked' => [
|
||||
@@ -142,9 +143,9 @@ class SystemModel extends AbstractMapTrackingModel {
|
||||
$systemData->type = $this->typeId->getData();
|
||||
}
|
||||
|
||||
$systemData->status = (object) [];
|
||||
$systemData->status->id = is_object($this->statusId) ? $this->statusId->id : 1;
|
||||
$systemData->status->name = is_object($this->statusId) ? $this->statusId->name : 'unknown';
|
||||
if(is_object($this->statusId)){
|
||||
$systemData->status = $this->statusId->getData();
|
||||
}
|
||||
|
||||
$systemData->locked = $this->locked;
|
||||
$systemData->rallyUpdated = strtotime($this->rallyUpdated);
|
||||
@@ -245,6 +246,22 @@ class SystemModel extends AbstractMapTrackingModel {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param int $val
|
||||
* @return bool
|
||||
* @throws \Exception\ValidationException
|
||||
*/
|
||||
protected function validate_statusId(string $key, int $val): bool {
|
||||
$valid = true;
|
||||
if( !$this->rel('statusId')::getStatusById($val) ){
|
||||
$valid = false;
|
||||
$this->throwValidationException($key, 'Validation failed: "' . $key . '" = "' . $val . '"');
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* setter for system alias
|
||||
* @param string $alias
|
||||
|
||||
@@ -77,4 +77,26 @@ class SystemStatusModel extends BasicModel {
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* get system status data
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function getData(){
|
||||
|
||||
$statusData = (object)[];
|
||||
$statusData->id = $this->_id;
|
||||
$statusData->name = $this->name;
|
||||
|
||||
return $statusData;
|
||||
}
|
||||
|
||||
/**
|
||||
* get status by id
|
||||
* @param int $statusId
|
||||
* @return self|null
|
||||
*/
|
||||
public static function getStatusById(int $statusId = 1){
|
||||
$status = (new self())->getById($statusId);
|
||||
return $status->dry() ? null : $status;
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ define([
|
||||
// dialogs
|
||||
systemDialogId: 'pf-system-dialog', // id for system dialog
|
||||
systemDialogSelectClass: 'pf-system-dialog-select', // class for system select Element
|
||||
systemDialogStatusSelectId: 'pf-system-dialog-status-select', // id for "status" select
|
||||
|
||||
// system security classes
|
||||
systemSec: 'pf-system-sec'
|
||||
@@ -1378,21 +1379,13 @@ define([
|
||||
* @param map
|
||||
* @param options
|
||||
*/
|
||||
let showNewSystemDialog = function(map, options){
|
||||
let showNewSystemDialog = (map, options) => {
|
||||
let mapContainer = $(map.getContainer());
|
||||
|
||||
// format system status for form select -----------------------------------------------------------------------
|
||||
let systemStatus = {};
|
||||
// "default" selection (id = 0) prevents status from being overwritten
|
||||
// -> e.g. keep status information if system was just inactive (active = 0)
|
||||
systemStatus[0] = 'default';
|
||||
|
||||
$.each(Init.systemStatus, function(status, statusData){
|
||||
systemStatus[statusData.id] = statusData.label;
|
||||
});
|
||||
|
||||
// default system status -> first status entry
|
||||
let defaultSystemStatus = 0;
|
||||
let statusData = [{id: 0, text: 'auto'}];
|
||||
|
||||
// get current map data ---------------------------------------------------------------------------------------
|
||||
let mapData = mapContainer.getMapDataFromClient({forceData: true});
|
||||
@@ -1416,7 +1409,10 @@ define([
|
||||
// dialog data ------------------------------------------------------------------------------------------------
|
||||
let data = {
|
||||
id: config.systemDialogId,
|
||||
selectClass: config.systemDialogSelectClass
|
||||
select2Class: Util.config.select2Class,
|
||||
selectClass: config.systemDialogSelectClass,
|
||||
statusSelectId: config.systemDialogStatusSelectId,
|
||||
statusData: statusData
|
||||
};
|
||||
|
||||
// set current position as "default" system to add ------------------------------------------------------------
|
||||
@@ -1438,6 +1434,7 @@ define([
|
||||
let systemDialog = bootbox.dialog({
|
||||
title: 'Add new system',
|
||||
message: content,
|
||||
show: false,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'cancel',
|
||||
@@ -1517,9 +1514,21 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
// init dialog
|
||||
systemDialog.on('shown.bs.modal', function(e) {
|
||||
systemDialog.on('show.bs.modal', function(e) {
|
||||
let modalContent = $('#' + config.systemDialogId);
|
||||
|
||||
// init "status" select2
|
||||
for (let [statusName, data] of Object.entries(Init.systemStatus)){
|
||||
statusData.push({id: data.id, text: data.label, class: data.class});
|
||||
}
|
||||
|
||||
modalContent.find('#' + config.systemDialogStatusSelectId).initStatusSelect({
|
||||
data: statusData,
|
||||
iconClass: 'fa-tag'
|
||||
});
|
||||
});
|
||||
|
||||
systemDialog.on('shown.bs.modal', function(e) {
|
||||
let modalContent = $('#' + config.systemDialogId);
|
||||
|
||||
// init system select live search - some delay until modal transition has finished
|
||||
@@ -1530,18 +1539,8 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
// init system status select
|
||||
let modalFields = $('.bootbox .modal-dialog').find('.pf-editable-system-status');
|
||||
|
||||
modalFields.editable({
|
||||
mode: 'inline',
|
||||
emptytext: 'unknown',
|
||||
onblur: 'submit',
|
||||
showbuttons: false,
|
||||
source: systemStatus,
|
||||
value: defaultSystemStatus,
|
||||
inputclass: config.systemDialogSelectClass
|
||||
});
|
||||
// show dialog
|
||||
systemDialog.modal('show');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ define([
|
||||
* @param data
|
||||
* @returns {*}
|
||||
*/
|
||||
let formatCategoryTypeResultData = (data) => {
|
||||
let formatCategoryTypeResultData = data => {
|
||||
if(data.loading) return data.text;
|
||||
if(data.placeholder) return data.placeholder;
|
||||
|
||||
@@ -82,6 +82,52 @@ define([
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* init a sselect element as "select2" for "status" selection
|
||||
* @param options
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.initStatusSelect = function(options){
|
||||
|
||||
let config = {
|
||||
minimumResultsForSearch: -1,
|
||||
width: '100%',
|
||||
iconClass: 'fa-circle'
|
||||
};
|
||||
|
||||
config = $.extend({}, config, options);
|
||||
|
||||
let formatStatusSelectionData = state => {
|
||||
let markup = '<span>';
|
||||
markup += '<i class="fas ' + config.iconClass + ' ' + state.class + '"></i> ' + state.text;
|
||||
markup += '</span>';
|
||||
|
||||
return $(markup);
|
||||
};
|
||||
|
||||
let formatStatusResultData = data => {
|
||||
if(data.loading) return data.text;
|
||||
if(data.placeholder) return data.placeholder;
|
||||
|
||||
let markup = '<div class="clearfix">';
|
||||
markup += '<div class="col-xs-2 text-center">';
|
||||
markup += '<i class="fas ' + config.iconClass + ' ' + data.class + '"></i>';
|
||||
markup += '</div>';
|
||||
markup += '<div class="col-xs-10">' + data.text + '</div>';
|
||||
markup += '</div>';
|
||||
|
||||
return $(markup);
|
||||
};
|
||||
|
||||
config.templateSelection = formatStatusSelectionData;
|
||||
config.templateResult = formatStatusResultData;
|
||||
|
||||
return this.each(function(){
|
||||
let selectElement = $(this);
|
||||
selectElement.select2(config);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init a select element as an ajax based "select2" object for system search
|
||||
* @param options
|
||||
@@ -304,7 +350,6 @@ define([
|
||||
// after init finish
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -242,14 +242,16 @@ define([
|
||||
let structureStatusData = Util.getObjVal(Init, 'structureStatus');
|
||||
let structureTypeData = Util.getObjVal(Init, 'structureStatus');
|
||||
|
||||
let statusData = Object.keys(structureStatusData).map((k) => {
|
||||
let data = structureStatusData[k];
|
||||
data.selected = data.id === Util.getObjVal(structureData, 'status.id');
|
||||
return data;
|
||||
});
|
||||
|
||||
let data = {
|
||||
id: config.structureDialogId,
|
||||
structureData: structureData,
|
||||
structureStatus: Object.keys(structureStatusData).map((k) => {
|
||||
let data = structureStatusData[k];
|
||||
data.selected = data.id === Util.getObjVal(structureData, 'status.id');
|
||||
return data;
|
||||
}),
|
||||
structureStatus: statusData,
|
||||
statusSelectId: config.statusSelectId,
|
||||
typeSelectId: config.typeSelectId,
|
||||
corporationSelectId: config.corporationSelectId,
|
||||
@@ -264,7 +266,7 @@ define([
|
||||
let structureDialog = bootbox.dialog({
|
||||
title: 'Structure',
|
||||
message: content,
|
||||
show: true,
|
||||
show: false,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'cancel',
|
||||
@@ -304,9 +306,11 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
structureDialog.on('shown.bs.modal', function(e) {
|
||||
structureDialog.on('show.bs.modal', function(e) {
|
||||
let modalContent = $('#' + config.structureDialogId);
|
||||
|
||||
// init type select live search
|
||||
let selectElementType = $(this).find('#' + config.typeSelectId);
|
||||
let selectElementType = modalContent.find('#' + config.typeSelectId);
|
||||
selectElementType.initUniverseTypeSelect({
|
||||
categoryIds: [65],
|
||||
maxSelectionLength: 1,
|
||||
@@ -314,19 +318,20 @@ define([
|
||||
});
|
||||
|
||||
// init corporation select live search
|
||||
let selectElementCorporation = $(this).find('#' + config.corporationSelectId);
|
||||
let selectElementCorporation = modalContent.find('#' + config.corporationSelectId);
|
||||
selectElementCorporation.initUniverseSearch({
|
||||
categoryNames: ['corporation'],
|
||||
maxSelectionLength: 1
|
||||
});
|
||||
|
||||
$(this).find('#' + config.statusSelectId).select2({
|
||||
minimumResultsForSearch: -1
|
||||
// init status select2
|
||||
modalContent.find('#' + config.statusSelectId).initStatusSelect({
|
||||
data: statusData
|
||||
});
|
||||
|
||||
// init character counter
|
||||
let textarea = $(this).find('#' + config.descriptionTextareaId);
|
||||
let charCounter = $(this).find('.' + config.descriptionTextareaCharCounter);
|
||||
let textarea = modalContent.find('#' + config.descriptionTextareaId);
|
||||
let charCounter = modalContent.find('.' + config.descriptionTextareaCharCounter);
|
||||
Util.updateCounter(textarea, charCounter, maxDescriptionLength);
|
||||
|
||||
textarea.on('keyup', function(){
|
||||
@@ -334,9 +339,11 @@ define([
|
||||
});
|
||||
|
||||
// set form validator (after select2 init finish)
|
||||
$(this).find('form').initFormValidation();
|
||||
modalContent.find('form').initFormValidation();
|
||||
});
|
||||
|
||||
// show dialog
|
||||
structureDialog.modal('show');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ define([
|
||||
// dialogs
|
||||
systemDialogId: 'pf-system-dialog', // id for system dialog
|
||||
systemDialogSelectClass: 'pf-system-dialog-select', // class for system select Element
|
||||
systemDialogStatusSelectId: 'pf-system-dialog-status-select', // id for "status" select
|
||||
|
||||
// system security classes
|
||||
systemSec: 'pf-system-sec'
|
||||
@@ -1378,21 +1379,13 @@ define([
|
||||
* @param map
|
||||
* @param options
|
||||
*/
|
||||
let showNewSystemDialog = function(map, options){
|
||||
let showNewSystemDialog = (map, options) => {
|
||||
let mapContainer = $(map.getContainer());
|
||||
|
||||
// format system status for form select -----------------------------------------------------------------------
|
||||
let systemStatus = {};
|
||||
// "default" selection (id = 0) prevents status from being overwritten
|
||||
// -> e.g. keep status information if system was just inactive (active = 0)
|
||||
systemStatus[0] = 'default';
|
||||
|
||||
$.each(Init.systemStatus, function(status, statusData){
|
||||
systemStatus[statusData.id] = statusData.label;
|
||||
});
|
||||
|
||||
// default system status -> first status entry
|
||||
let defaultSystemStatus = 0;
|
||||
let statusData = [{id: 0, text: 'auto'}];
|
||||
|
||||
// get current map data ---------------------------------------------------------------------------------------
|
||||
let mapData = mapContainer.getMapDataFromClient({forceData: true});
|
||||
@@ -1416,7 +1409,10 @@ define([
|
||||
// dialog data ------------------------------------------------------------------------------------------------
|
||||
let data = {
|
||||
id: config.systemDialogId,
|
||||
selectClass: config.systemDialogSelectClass
|
||||
select2Class: Util.config.select2Class,
|
||||
selectClass: config.systemDialogSelectClass,
|
||||
statusSelectId: config.systemDialogStatusSelectId,
|
||||
statusData: statusData
|
||||
};
|
||||
|
||||
// set current position as "default" system to add ------------------------------------------------------------
|
||||
@@ -1438,6 +1434,7 @@ define([
|
||||
let systemDialog = bootbox.dialog({
|
||||
title: 'Add new system',
|
||||
message: content,
|
||||
show: false,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'cancel',
|
||||
@@ -1517,9 +1514,21 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
// init dialog
|
||||
systemDialog.on('shown.bs.modal', function(e) {
|
||||
systemDialog.on('show.bs.modal', function(e) {
|
||||
let modalContent = $('#' + config.systemDialogId);
|
||||
|
||||
// init "status" select2
|
||||
for (let [statusName, data] of Object.entries(Init.systemStatus)){
|
||||
statusData.push({id: data.id, text: data.label, class: data.class});
|
||||
}
|
||||
|
||||
modalContent.find('#' + config.systemDialogStatusSelectId).initStatusSelect({
|
||||
data: statusData,
|
||||
iconClass: 'fa-tag'
|
||||
});
|
||||
});
|
||||
|
||||
systemDialog.on('shown.bs.modal', function(e) {
|
||||
let modalContent = $('#' + config.systemDialogId);
|
||||
|
||||
// init system select live search - some delay until modal transition has finished
|
||||
@@ -1530,18 +1539,8 @@ define([
|
||||
});
|
||||
});
|
||||
|
||||
// init system status select
|
||||
let modalFields = $('.bootbox .modal-dialog').find('.pf-editable-system-status');
|
||||
|
||||
modalFields.editable({
|
||||
mode: 'inline',
|
||||
emptytext: 'unknown',
|
||||
onblur: 'submit',
|
||||
showbuttons: false,
|
||||
source: systemStatus,
|
||||
value: defaultSystemStatus,
|
||||
inputclass: config.systemDialogSelectClass
|
||||
});
|
||||
// show dialog
|
||||
systemDialog.modal('show');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ define([
|
||||
* @param data
|
||||
* @returns {*}
|
||||
*/
|
||||
let formatCategoryTypeResultData = (data) => {
|
||||
let formatCategoryTypeResultData = data => {
|
||||
if(data.loading) return data.text;
|
||||
if(data.placeholder) return data.placeholder;
|
||||
|
||||
@@ -82,6 +82,52 @@ define([
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* init a sselect element as "select2" for "status" selection
|
||||
* @param options
|
||||
* @returns {*}
|
||||
*/
|
||||
$.fn.initStatusSelect = function(options){
|
||||
|
||||
let config = {
|
||||
minimumResultsForSearch: -1,
|
||||
width: '100%',
|
||||
iconClass: 'fa-circle'
|
||||
};
|
||||
|
||||
config = $.extend({}, config, options);
|
||||
|
||||
let formatStatusSelectionData = state => {
|
||||
let markup = '<span>';
|
||||
markup += '<i class="fas ' + config.iconClass + ' ' + state.class + '"></i> ' + state.text;
|
||||
markup += '</span>';
|
||||
|
||||
return $(markup);
|
||||
};
|
||||
|
||||
let formatStatusResultData = data => {
|
||||
if(data.loading) return data.text;
|
||||
if(data.placeholder) return data.placeholder;
|
||||
|
||||
let markup = '<div class="clearfix">';
|
||||
markup += '<div class="col-xs-2 text-center">';
|
||||
markup += '<i class="fas ' + config.iconClass + ' ' + data.class + '"></i>';
|
||||
markup += '</div>';
|
||||
markup += '<div class="col-xs-10">' + data.text + '</div>';
|
||||
markup += '</div>';
|
||||
|
||||
return $(markup);
|
||||
};
|
||||
|
||||
config.templateSelection = formatStatusSelectionData;
|
||||
config.templateResult = formatStatusResultData;
|
||||
|
||||
return this.each(function(){
|
||||
let selectElement = $(this);
|
||||
selectElement.select2(config);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* init a select element as an ajax based "select2" object for system search
|
||||
* @param options
|
||||
@@ -304,7 +350,6 @@ define([
|
||||
// after init finish
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -242,14 +242,16 @@ define([
|
||||
let structureStatusData = Util.getObjVal(Init, 'structureStatus');
|
||||
let structureTypeData = Util.getObjVal(Init, 'structureStatus');
|
||||
|
||||
let statusData = Object.keys(structureStatusData).map((k) => {
|
||||
let data = structureStatusData[k];
|
||||
data.selected = data.id === Util.getObjVal(structureData, 'status.id');
|
||||
return data;
|
||||
});
|
||||
|
||||
let data = {
|
||||
id: config.structureDialogId,
|
||||
structureData: structureData,
|
||||
structureStatus: Object.keys(structureStatusData).map((k) => {
|
||||
let data = structureStatusData[k];
|
||||
data.selected = data.id === Util.getObjVal(structureData, 'status.id');
|
||||
return data;
|
||||
}),
|
||||
structureStatus: statusData,
|
||||
statusSelectId: config.statusSelectId,
|
||||
typeSelectId: config.typeSelectId,
|
||||
corporationSelectId: config.corporationSelectId,
|
||||
@@ -264,7 +266,7 @@ define([
|
||||
let structureDialog = bootbox.dialog({
|
||||
title: 'Structure',
|
||||
message: content,
|
||||
show: true,
|
||||
show: false,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'cancel',
|
||||
@@ -304,9 +306,11 @@ define([
|
||||
}
|
||||
});
|
||||
|
||||
structureDialog.on('shown.bs.modal', function(e) {
|
||||
structureDialog.on('show.bs.modal', function(e) {
|
||||
let modalContent = $('#' + config.structureDialogId);
|
||||
|
||||
// init type select live search
|
||||
let selectElementType = $(this).find('#' + config.typeSelectId);
|
||||
let selectElementType = modalContent.find('#' + config.typeSelectId);
|
||||
selectElementType.initUniverseTypeSelect({
|
||||
categoryIds: [65],
|
||||
maxSelectionLength: 1,
|
||||
@@ -314,19 +318,20 @@ define([
|
||||
});
|
||||
|
||||
// init corporation select live search
|
||||
let selectElementCorporation = $(this).find('#' + config.corporationSelectId);
|
||||
let selectElementCorporation = modalContent.find('#' + config.corporationSelectId);
|
||||
selectElementCorporation.initUniverseSearch({
|
||||
categoryNames: ['corporation'],
|
||||
maxSelectionLength: 1
|
||||
});
|
||||
|
||||
$(this).find('#' + config.statusSelectId).select2({
|
||||
minimumResultsForSearch: -1
|
||||
// init status select2
|
||||
modalContent.find('#' + config.statusSelectId).initStatusSelect({
|
||||
data: statusData
|
||||
});
|
||||
|
||||
// init character counter
|
||||
let textarea = $(this).find('#' + config.descriptionTextareaId);
|
||||
let charCounter = $(this).find('.' + config.descriptionTextareaCharCounter);
|
||||
let textarea = modalContent.find('#' + config.descriptionTextareaId);
|
||||
let charCounter = modalContent.find('.' + config.descriptionTextareaCharCounter);
|
||||
Util.updateCounter(textarea, charCounter, maxDescriptionLength);
|
||||
|
||||
textarea.on('keyup', function(){
|
||||
@@ -334,9 +339,11 @@ define([
|
||||
});
|
||||
|
||||
// set form validator (after select2 init finish)
|
||||
$(this).find('form').initFormValidation();
|
||||
modalContent.find('form').initFormValidation();
|
||||
});
|
||||
|
||||
// show dialog
|
||||
structureDialog.modal('show');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -46,9 +46,13 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="form_status">Status</label>
|
||||
<div class="col-sm-6" style="min-height: 32px;">
|
||||
<a class="pf-editable pf-editable-system-status" href="#" id="form_status" data-type="select" data-name="statusId"></a>
|
||||
<label class="col-sm-2 control-label" for="{{statusSelectId}}">Status</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="statusId" id="{{statusSelectId}}" class="form-control {{select2Class}}">
|
||||
{{#statusData}}
|
||||
<option value="{{id}}">{{text}}</option>
|
||||
{{/statusData}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user