CRUD Taxonomia (#1)
Co-authored-by: Juan Felipe Zapata Moreno <zapata_pipe@hotmail.com> Reviewed-on: #1
This commit is contained in:
parent
be89d18e74
commit
0a526df665
73
app/Http/Controllers/Admin/DepartmentController.php
Normal file
73
app/Http/Controllers/Admin/DepartmentController.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||
*/
|
||||
|
||||
use App\Http\Requests\StoreDepartment;
|
||||
use App\Http\Requests\UpdateDepartment;
|
||||
use App\Models\department;
|
||||
use Notsoweb\Core\Http\Controllers\VueController;
|
||||
|
||||
/**
|
||||
* Descripción
|
||||
*
|
||||
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class DepartmentController extends VueController
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->vueRoot('admin.departments');
|
||||
}
|
||||
|
||||
|
||||
public function index()
|
||||
{
|
||||
$q = request()->get('q');
|
||||
|
||||
return $this->vuew('index', [
|
||||
'departments' => department::where('name', 'LIKE', "%{$q}%")
|
||||
->orWhere('description', 'LIKE', "%{$q}%")
|
||||
->select([
|
||||
'id',
|
||||
'name',
|
||||
'description',
|
||||
])
|
||||
->paginate(config('app.pagination'))
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return $this->vuew('create');
|
||||
}
|
||||
|
||||
public function store(StoreDepartment $request)
|
||||
{
|
||||
$data = $request->all();
|
||||
|
||||
department::create($data);
|
||||
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
public function update(UpdateDepartment $request, $department)
|
||||
{
|
||||
$data = $request->all();
|
||||
|
||||
department::find($department)->update($data);
|
||||
}
|
||||
|
||||
public function destroy($department)
|
||||
{
|
||||
department::find($department)->delete();
|
||||
|
||||
return $this->index();
|
||||
}
|
||||
}
|
||||
74
app/Http/Controllers/Admin/MainRoleController.php
Normal file
74
app/Http/Controllers/Admin/MainRoleController.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||
*/
|
||||
|
||||
use App\Http\Requests\StoreMainRole;
|
||||
use App\Http\Requests\UpdateMainRole;
|
||||
use App\Models\department;
|
||||
use App\Models\mainRole;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Notsoweb\Core\Http\Controllers\VueController;
|
||||
|
||||
/**
|
||||
* Descripción
|
||||
*
|
||||
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class MainRoleController extends VueController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->vueRoot('admin.mainRole');
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$q = request()->get('q');
|
||||
|
||||
$mainRoles = mainRole::orderBy('name', 'ASC')
|
||||
->where('name', 'LIKE', "%{$q}%")
|
||||
->with('department:id,name')
|
||||
->paginate(config('app.pagination'));
|
||||
|
||||
return $this->vuew('index', [
|
||||
'mainRoles' => $mainRoles,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$department = department::orderBy('name', 'ASC')->get();
|
||||
|
||||
return $this->vuew('create', [
|
||||
'departments' => $department,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreMainRole $request)
|
||||
{
|
||||
mainRole::create($request->all());
|
||||
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
public function update(UpdateMainRole $request, mainRole $mainRole)
|
||||
{
|
||||
$mainRole->update($request->all());
|
||||
}
|
||||
|
||||
public function destroy(mainRole $mainRole)
|
||||
{ try{
|
||||
$mainRole = mainRole::find($mainRole);
|
||||
$mainRole->delete();
|
||||
}catch (\Throwable $th) {
|
||||
Log::channel('mainRole')->error($th->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
71
app/Http/Controllers/Admin/ScoreController.php
Normal file
71
app/Http/Controllers/Admin/ScoreController.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php namespace App\Http\Controllers\Admin;
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||
*/
|
||||
|
||||
use App\Models\Score;
|
||||
use App\Http\Requests\StoreScore;
|
||||
use App\Http\Requests\UpdateScore;
|
||||
use Notsoweb\Core\Http\Controllers\VueController;
|
||||
|
||||
/**
|
||||
* Descripción
|
||||
*
|
||||
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class ScoreController extends VueController
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->vueRoot('admin.app');
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$q = request()->get('q');
|
||||
|
||||
return $this->vuew('index', [
|
||||
'scores' => Score::where('alias', 'LIKE', "%{$q}%")
|
||||
->orWhere('value', 'LIKE', "%{$q}%")
|
||||
->select([
|
||||
'id',
|
||||
'alias',
|
||||
'value',
|
||||
'description',
|
||||
])
|
||||
->paginate(config('app.pagination'))
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return $this->vuew('create');
|
||||
}
|
||||
|
||||
public function store(StoreScore $request)
|
||||
{
|
||||
$data = $request->all();
|
||||
|
||||
Score::create($data);
|
||||
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
public function update(UpdateScore $request, $score)
|
||||
{
|
||||
$data = $request->all();
|
||||
|
||||
Score::find($score)->update($data);
|
||||
}
|
||||
|
||||
public function destroy($score)
|
||||
{
|
||||
Score::find($score)->delete();
|
||||
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
}
|
||||
39
app/Http/Requests/StoreDepartment.php
Normal file
39
app/Http/Requests/StoreDepartment.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php namespace App\Http\Requests;
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||
*/
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Valida el almacenamiento de un usuario
|
||||
*
|
||||
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class StoreDepartment extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determinar si el usuario esta autorizado
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reglas de validación
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string'],
|
||||
'description' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
40
app/Http/Requests/StoreMainRole.php
Normal file
40
app/Http/Requests/StoreMainRole.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php namespace App\Http\Requests;
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||
*/
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Valida el almacenamiento de un usuario
|
||||
*
|
||||
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class StoreMainRole extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determinar si el usuario esta autorizado
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reglas de validación
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'department_id' => ['required', 'integer'],
|
||||
];
|
||||
}
|
||||
}
|
||||
40
app/Http/Requests/StoreScore.php
Normal file
40
app/Http/Requests/StoreScore.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php namespace App\Http\Requests;
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||
*/
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Valida el almacenamiento de un usuario
|
||||
*
|
||||
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class StoreScore extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determinar si el usuario esta autorizado
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reglas de validación
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'alias' => ['required', 'string'],
|
||||
'value' => ['required', 'string'],
|
||||
'description' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
40
app/Http/Requests/UpdateDepartment.php
Normal file
40
app/Http/Requests/UpdateDepartment.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php namespace App\Http\Requests;
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||
*/
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* Valida la actualización de los usuarios
|
||||
*
|
||||
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class UpdateDepartment extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determinar si el usuario esta autorizado
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reglas de validación
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string'],
|
||||
'description' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
40
app/Http/Requests/UpdateMainRole.php
Normal file
40
app/Http/Requests/UpdateMainRole.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php namespace App\Http\Requests;
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||
*/
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Valida el almacenamiento de un usuario
|
||||
*
|
||||
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class UpdateMainRole extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determinar si el usuario esta autorizado
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reglas de validación
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'department_id' => ['required', 'integer','exists:departments,id'],
|
||||
];
|
||||
}
|
||||
}
|
||||
41
app/Http/Requests/UpdateScore.php
Normal file
41
app/Http/Requests/UpdateScore.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php namespace App\Http\Requests;
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||
*/
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* Valida la actualización de los usuarios
|
||||
*
|
||||
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class UpdateScore extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determinar si el usuario esta autorizado
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reglas de validación
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'alias' => ['required', 'string'],
|
||||
'value' => ['required', 'string'],
|
||||
'description' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
30
app/Models/Score.php
Normal file
30
app/Models/Score.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php namespace App\Models;
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||
*/
|
||||
|
||||
use App\Http\Traits\ModelExtend;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Descripción
|
||||
*
|
||||
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class Score extends Model
|
||||
{
|
||||
use HasFactory,
|
||||
ModelExtend;
|
||||
|
||||
/**
|
||||
* Atributos llenables masivamente
|
||||
*/
|
||||
protected $fillable = [
|
||||
'alias',
|
||||
'value',
|
||||
'description',
|
||||
];
|
||||
}
|
||||
37
app/Models/department.php
Normal file
37
app/Models/department.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php namespace App\Models;
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||
*/
|
||||
|
||||
use App\Http\Traits\ModelExtend;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Descripción
|
||||
*
|
||||
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class department extends Model
|
||||
{
|
||||
use HasFactory,
|
||||
ModelExtend;
|
||||
|
||||
/**
|
||||
* Atributos llenables masivamente
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description'
|
||||
];
|
||||
|
||||
/**
|
||||
* Un departamento tiene muchos main roles
|
||||
*/
|
||||
public function mainRoles()
|
||||
{
|
||||
return $this->hasMany(MainRole::class);
|
||||
}
|
||||
}
|
||||
43
app/Models/mainRole.php
Normal file
43
app/Models/mainRole.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php namespace App\Models;
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||
*/
|
||||
|
||||
use App\Http\Traits\ModelExtend;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Roles principales del sistema
|
||||
*
|
||||
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
class MainRole extends Model
|
||||
{
|
||||
use HasFactory,
|
||||
ModelExtend;
|
||||
|
||||
/**
|
||||
* Nombre de la tabla
|
||||
*/
|
||||
protected $table = 'main_role';
|
||||
|
||||
/**
|
||||
* Atributos llenables masivamente
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'department_id'
|
||||
];
|
||||
|
||||
/**
|
||||
* Un main role pertenece a un departamento
|
||||
*/
|
||||
public function department()
|
||||
{
|
||||
return $this->belongsTo(department::class);
|
||||
}
|
||||
}
|
||||
34
database/migrations/2025_07_01_104541_scores.php
Normal file
34
database/migrations/2025_07_01_104541_scores.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('scores', function (Blueprint $table){
|
||||
$table->id();
|
||||
$table->string('alias');
|
||||
$table->integer('value');
|
||||
$table->string('description')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('scores');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('departments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('description')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('departments');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('main_role', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('description')->nullable();
|
||||
$table->foreignId('department_id')->constrained('departments')->onDelete('cascade');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('main_role');
|
||||
}
|
||||
};
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "template-laravel-vuejs",
|
||||
"name": "Scores",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@ -224,5 +224,34 @@ export default {
|
||||
system:'Usuarios del sistema',
|
||||
title:'Usuarios',
|
||||
},
|
||||
|
||||
scores:{
|
||||
system: 'Sistema de puntuación',
|
||||
create:{
|
||||
title:'Crear puntuación',
|
||||
description:'Permite crear nuevas puntuaciones para los usuarios.',
|
||||
onSuccess:'Puntuación creada exitosamente',
|
||||
onError:'Error al crear la puntuación',
|
||||
}
|
||||
},
|
||||
department:{
|
||||
system: 'Sistema de departamentos',
|
||||
title: 'Departamentos',
|
||||
create:{
|
||||
title:'Crear departamento',
|
||||
description:'Permite crear nuevos departamentos para los usuarios.',
|
||||
onSuccess:'Departamento creado exitosamente',
|
||||
onError:'Error al crear el departamento',
|
||||
}
|
||||
},
|
||||
mainRole: {
|
||||
system: 'Sistema de roles principales',
|
||||
create:{
|
||||
title:'Crear rol principal',
|
||||
description:'Permite crear nuevos roles principales para los usuarios.',
|
||||
onSuccess:'Rol principal creado exitosamente',
|
||||
onError:'Error al crear el rol principal',
|
||||
},
|
||||
},
|
||||
version:'Versión',
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ onMounted(()=> {
|
||||
:title="title"
|
||||
/>
|
||||
<div class="flex w-full h-screen">
|
||||
<div
|
||||
<div
|
||||
id="sidebar"
|
||||
class="fixed w-fit h-screen transition-all duration-300 z-10"
|
||||
:class="{'-translate-x-[16.5rem] md:-translate-x-0':sidebarStatus, '-translate-x-0 md:-translate-x-64':!sidebarStatus}"
|
||||
@ -45,9 +45,9 @@ onMounted(()=> {
|
||||
@open="sidebarSwitch()"
|
||||
>
|
||||
<Section name="Principal">
|
||||
<Link
|
||||
<Link
|
||||
icon="home"
|
||||
name="home"
|
||||
name="home"
|
||||
to="dashboard.index"
|
||||
/>
|
||||
<Link
|
||||
@ -57,7 +57,7 @@ onMounted(()=> {
|
||||
/>
|
||||
</Section>
|
||||
<Section name="Configuraciones">
|
||||
<Link
|
||||
<Link
|
||||
icon="manage_accounts"
|
||||
name="profile"
|
||||
to="profile.show"
|
||||
@ -69,12 +69,27 @@ onMounted(()=> {
|
||||
to="dashboard.histories.index"
|
||||
/>
|
||||
</Section>
|
||||
<Link
|
||||
<Link
|
||||
v-if="hasPermission('users.index')"
|
||||
icon="people"
|
||||
name="users.title"
|
||||
to="admin.users.index"
|
||||
/>
|
||||
<Link
|
||||
icon="business_center"
|
||||
name="Departamentos"
|
||||
to="admin.departments.index"
|
||||
/>
|
||||
<Link
|
||||
icon="assignment_ind"
|
||||
name="Rol Principal"
|
||||
to="admin.mainRoles.index"
|
||||
/>
|
||||
<Link
|
||||
icon="leaderboard"
|
||||
name="Scores"
|
||||
to="admin.scores.index"
|
||||
/>
|
||||
<Link
|
||||
icon="history"
|
||||
name="changelogs.title"
|
||||
@ -93,7 +108,7 @@ onMounted(()=> {
|
||||
</Section>
|
||||
</Sidebar>
|
||||
</div>
|
||||
<div
|
||||
<div
|
||||
class="flex flex-col w-full transition-all duration-300"
|
||||
:class="{'md:w-[calc(100vw-rem)] md:ml-64':sidebarStatus, 'md:w-screen md:ml-0':!sidebarStatus}"
|
||||
>
|
||||
|
||||
15
resources/js/Pages/Admin/App/Component.js
Normal file
15
resources/js/Pages/Admin/App/Component.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { t } from '@/Lang/i18n';
|
||||
import { hasPermission } from '@/rolePermission.js';
|
||||
|
||||
// Obtener ruta
|
||||
const goTo = (route) => `admin.scores.${route}`
|
||||
// Obtener traducción del componente
|
||||
const transl = (lang) => t(`scores.${lang}`)
|
||||
// Determina si un usuario puede hacer algo no en base a los permisos
|
||||
const can = (permission) => hasPermission(`users.${permission}`)
|
||||
|
||||
export {
|
||||
can,
|
||||
goTo,
|
||||
transl
|
||||
}
|
||||
84
resources/js/Pages/Admin/App/Create.vue
Normal file
84
resources/js/Pages/Admin/App/Create.vue
Normal file
@ -0,0 +1,84 @@
|
||||
<script setup>
|
||||
import { goTo, transl } from './Component';
|
||||
import { Link, useForm } from '@inertiajs/vue3';
|
||||
|
||||
import PrimaryButton from '@/Components/Dashboard/Button/Primary.vue';
|
||||
import Input from '@/Components/Dashboard/Form/Input.vue';
|
||||
import PageHeader from '@/Components/Dashboard/PageHeader.vue';
|
||||
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
|
||||
import DashboardLayout from '@/Layouts/DashboardLayout.vue';
|
||||
|
||||
defineProps({
|
||||
alias: String,
|
||||
value: Number,
|
||||
description: String
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
alias: '',
|
||||
value: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
const submit = () => form.post(route(goTo('store')), {
|
||||
onSuccess: () => Notify.success(transl('create.onSuccess')),
|
||||
onError: () => Notify.error(transl('create.onError')),
|
||||
onFinish: () => form.reset('password')
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout :title="transl('create.title')">
|
||||
<PageHeader>
|
||||
<Link :href="route(goTo('index'))">
|
||||
<GoogleIcon
|
||||
:title="$t('return')"
|
||||
class="btn-icon-primary"
|
||||
name="arrow_back"
|
||||
outline
|
||||
/>
|
||||
</Link>
|
||||
</PageHeader>
|
||||
<div class="w-full pb-8">
|
||||
<div class="mt-8">
|
||||
<p
|
||||
v-text="transl('create.description')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<form @submit.prevent="submit" class="grid gap-4 grid-cols-6">
|
||||
<Input
|
||||
id="Alias"
|
||||
class="col-span-2"
|
||||
v-model="form.alias"
|
||||
:onError="form.errors.alias"
|
||||
autofocus
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
id="Valor"
|
||||
class="col-span-2"
|
||||
v-model="form.value"
|
||||
:onError="form.errors.value"
|
||||
autofocus
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
id="Descrición"
|
||||
class="col-span-2"
|
||||
v-model="form.description"
|
||||
:onError="form.errors.description"
|
||||
autofocus
|
||||
/>
|
||||
<div class="col-span-6 flex flex-col items-center justify-end space-y-4 mt-4">
|
||||
<PrimaryButton
|
||||
:class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing"
|
||||
v-text="transl('create.title')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
43
resources/js/Pages/Admin/App/Destroy.vue
Normal file
43
resources/js/Pages/Admin/App/Destroy.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
import { transl, goTo } from './Component'
|
||||
import { router } from '@inertiajs/vue3';
|
||||
|
||||
import DestroyModal from '@/Components/Dashboard/Modal/Destroy.vue';
|
||||
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
|
||||
|
||||
const emit = defineEmits([
|
||||
'close',
|
||||
'switchModal'
|
||||
]);
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
model: Object
|
||||
});
|
||||
|
||||
const destroy = (id) => router.delete(route(goTo('destroy'), {id}), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
props.model.pop;
|
||||
Notify.success(transl('deleted'));
|
||||
emit('close');
|
||||
},
|
||||
onError: () => {
|
||||
Notify.info(transl('notFound'));
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DestroyModal
|
||||
:show="show"
|
||||
@close="$emit('close')"
|
||||
@destroy="destroy(model.id)"
|
||||
>
|
||||
<Header
|
||||
:title="model.alias"
|
||||
:subtitle="model.value"
|
||||
/>
|
||||
</DestroyModal>
|
||||
</template>
|
||||
73
resources/js/Pages/Admin/App/Edit.vue
Normal file
73
resources/js/Pages/Admin/App/Edit.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<script setup>
|
||||
import { goTo } from './Component';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { onUpdated } from 'vue';
|
||||
|
||||
import Input from '@/Components/Dashboard/Form/Input.vue';
|
||||
import EditModal from '@/Components/Dashboard/Modal/Edit.vue';
|
||||
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
|
||||
|
||||
const emit = defineEmits([
|
||||
'close',
|
||||
'switchModal'
|
||||
]);
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
model: Object
|
||||
});
|
||||
|
||||
const form = useForm({});
|
||||
|
||||
const update = (id) => {
|
||||
form.transform(data => ({
|
||||
...props.model
|
||||
})).put(route(goTo('update'), {id}),{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
Notify.success(lang('updated'))
|
||||
emit('switchModal')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<EditModal
|
||||
:show="show"
|
||||
@close="$emit('close')"
|
||||
@update="update(model.id)"
|
||||
>
|
||||
<Header
|
||||
:title="model.alias"
|
||||
/>
|
||||
<div class="py-2 border-b">
|
||||
<div class="p-4">
|
||||
<form>
|
||||
<div class="grid gap-6 mb-6 lg:grid-cols-2">
|
||||
<Input
|
||||
id="Alias"
|
||||
placeholder="Alias"
|
||||
v-model="model.alias"
|
||||
:onError="form.errors.alias"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
id="Valor"
|
||||
v-model="model.value"
|
||||
:onError="form.errors.value"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
id="Descripción"
|
||||
v-model="model.description"
|
||||
:onError="form.errors.description"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</EditModal>
|
||||
</template>
|
||||
158
resources/js/Pages/Admin/App/Index.vue
Normal file
158
resources/js/Pages/Admin/App/Index.vue
Normal file
@ -0,0 +1,158 @@
|
||||
<script setup>
|
||||
import { transl, can, goTo } from './Component'
|
||||
import { ref } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
|
||||
import ModalController from '@/Controllers/ModalController.js';
|
||||
import SearcherController from '@/Controllers/SearcherController.js';
|
||||
|
||||
import SearcherHead from '@/Components/Dashboard/Searcher.vue';
|
||||
import Table from '@/Components/Dashboard/Table.vue';
|
||||
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
|
||||
import DashboardLayout from '@/Layouts/DashboardLayout.vue';
|
||||
import DestroyView from './Destroy.vue';
|
||||
import EditView from './Edit.vue';
|
||||
import ShowView from './Show.vue';
|
||||
|
||||
const props = defineProps({
|
||||
scores: Object
|
||||
});
|
||||
|
||||
// Controladores
|
||||
const Modal = new ModalController();
|
||||
const Searcher = new SearcherController(goTo('index'));
|
||||
|
||||
// Variables de controladores
|
||||
const destroyModal = ref(Modal.destroyModal);
|
||||
const editModal = ref(Modal.editModal);
|
||||
const showModal = ref(Modal.showModal);
|
||||
const modelModal = ref(Modal.modelModal);
|
||||
const query = ref(Searcher.query);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout :title="transl('system')">
|
||||
<SearcherHead @search="Searcher.search">
|
||||
<Link
|
||||
v-if="can('create')"
|
||||
:href="route(goTo('create'))"
|
||||
>
|
||||
<GoogleIcon
|
||||
:title="$t('crud.create')"
|
||||
class="btn-icon-primary"
|
||||
name="add"
|
||||
outline
|
||||
/>
|
||||
</Link>
|
||||
</SearcherHead>
|
||||
<div class="pt-2 w-full">
|
||||
<Table
|
||||
:items="scores"
|
||||
@send-pagination="Searcher.searchWithPagination"
|
||||
>
|
||||
<template #head>
|
||||
<th
|
||||
class="table-item"
|
||||
v-text="$t('Alias')"
|
||||
/>
|
||||
<th
|
||||
class="table-item"
|
||||
v-text="$t('Valor')"
|
||||
/>
|
||||
<th
|
||||
class="table-item"
|
||||
v-text="$t('Descripción')"
|
||||
/>
|
||||
<th
|
||||
class="table-item w-44"
|
||||
v-text="$t('actions')"
|
||||
/>
|
||||
</template>
|
||||
<template #body="{items}">
|
||||
<tr v-for="model in items">
|
||||
<td class="table-item border">
|
||||
<div class="flex items-center text-sm">
|
||||
<div>
|
||||
<p class="font-semibold">
|
||||
{{ model.alias }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-item border">
|
||||
<div class="flex items-center text-sm">
|
||||
<div>
|
||||
<p class="font-semibold">
|
||||
{{ model.value }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-item border">
|
||||
<div class="flex items-center text-sm">
|
||||
<div>
|
||||
<p class="font-semibold">
|
||||
{{ model.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-item border">
|
||||
<div class="flex justify-center space-x-2">
|
||||
<GoogleIcon
|
||||
:title="$t('crud.show')"
|
||||
class="btn-icon-primary"
|
||||
name="visibility"
|
||||
outline
|
||||
@click="Modal.switchShowModal(model)"
|
||||
/>
|
||||
<GoogleIcon
|
||||
v-if="can('edit')"
|
||||
:title="$t('crud.edit')"
|
||||
class="btn-icon-primary"
|
||||
name="edit"
|
||||
outline
|
||||
@click="Modal.switchEditModal(model)"
|
||||
/>
|
||||
<GoogleIcon
|
||||
v-if="can('destroy')"
|
||||
:title="$t('crud.destroy')"
|
||||
class="btn-icon-primary"
|
||||
name="delete"
|
||||
outline
|
||||
@click="Modal.switchDestroyModal(model)"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template #empty>
|
||||
<td class="table-item border">
|
||||
<div class="flex items-center text-sm">
|
||||
<div>
|
||||
<p class="font-semibold">
|
||||
{{ $t('registers.empty') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-item border">-</td>
|
||||
<td class="table-item border">-</td>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
<EditView
|
||||
v-if="can('edit')"
|
||||
:show="editModal"
|
||||
:model="modelModal"
|
||||
@switchModal="Modal.switchShowEditModal"
|
||||
@close="Modal.switchEditModal"
|
||||
/>
|
||||
<DestroyView
|
||||
v-if="can('create')"
|
||||
:show="destroyModal"
|
||||
:model="modelModal"
|
||||
@close="Modal.switchDestroyModal"
|
||||
/>
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
41
resources/js/Pages/Admin/App/Show.vue
Normal file
41
resources/js/Pages/Admin/App/Show.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
import { can } from './Component'
|
||||
|
||||
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
|
||||
import ShowModal from '@/Components/Dashboard/Modal/Show.vue';
|
||||
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
|
||||
|
||||
defineEmits([
|
||||
'close',
|
||||
'switchModal'
|
||||
]);
|
||||
|
||||
defineProps({
|
||||
show: Boolean,
|
||||
model: Object
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<ShowModal
|
||||
:show="show"
|
||||
:editable="can('edit')"
|
||||
@close="$emit('close')"
|
||||
@edit="$emit('switchModal')"
|
||||
editable
|
||||
>
|
||||
<Header
|
||||
:title="model.alias"
|
||||
>
|
||||
</Header>
|
||||
<div class="py-2 border-b">
|
||||
<div class="px-4 py-2 flex">
|
||||
<GoogleIcon
|
||||
class="text-xl text-success"
|
||||
name="contact_mail"
|
||||
/>
|
||||
<div class="pl-3">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ShowModal>
|
||||
</template>
|
||||
15
resources/js/Pages/Admin/Departments/Component.js
Normal file
15
resources/js/Pages/Admin/Departments/Component.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { t } from '@/Lang/i18n';
|
||||
import { hasPermission } from '@/rolePermission.js';
|
||||
|
||||
// Obtener ruta
|
||||
const goTo = (route) => `admin.departments.${route}`
|
||||
// Obtener traducción del componente
|
||||
const transl = (lang) => t(`department.${lang}`)
|
||||
// Determina si un usuario puede hacer algo no en base a los permisos
|
||||
const can = (permission) => hasPermission(`users.${permission}`)
|
||||
|
||||
export {
|
||||
can,
|
||||
goTo,
|
||||
transl
|
||||
}
|
||||
73
resources/js/Pages/Admin/Departments/Create.vue
Normal file
73
resources/js/Pages/Admin/Departments/Create.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<script setup>
|
||||
import { goTo, transl } from './Component';
|
||||
import { Link, useForm } from '@inertiajs/vue3';
|
||||
|
||||
import PrimaryButton from '@/Components/Dashboard/Button/Primary.vue';
|
||||
import Input from '@/Components/Dashboard/Form/Input.vue';
|
||||
import PageHeader from '@/Components/Dashboard/PageHeader.vue';
|
||||
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
|
||||
import DashboardLayout from '@/Layouts/DashboardLayout.vue';
|
||||
|
||||
defineProps({
|
||||
name: String,
|
||||
description: String
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
name: '',
|
||||
description: '',
|
||||
});
|
||||
|
||||
const submit = () => form.post(route(goTo('store')), {
|
||||
onSuccess: () => Notify.success(transl('create.onSuccess')),
|
||||
onError: () => Notify.error(transl('create.onError')),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout :title="transl('create.title')">
|
||||
<PageHeader>
|
||||
<Link :href="route(goTo('index'))">
|
||||
<GoogleIcon
|
||||
:title="$t('return')"
|
||||
class="btn-icon-primary"
|
||||
name="arrow_back"
|
||||
outline
|
||||
/>
|
||||
</Link>
|
||||
</PageHeader>
|
||||
<div class="w-full pb-8">
|
||||
<div class="mt-8">
|
||||
<p
|
||||
v-text="transl('create.description')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<form @submit.prevent="submit" class="grid gap-4 grid-cols-6">
|
||||
<Input
|
||||
id="Nombre"
|
||||
class="col-span-2"
|
||||
v-model="form.name"
|
||||
:onError="form.errors.name"
|
||||
autofocus
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
id="Descrición"
|
||||
class="col-span-2"
|
||||
v-model="form.description"
|
||||
:onError="form.errors.description"
|
||||
autofocus
|
||||
/>
|
||||
<div class="col-span-6 flex flex-col items-center justify-end space-y-4 mt-4">
|
||||
<PrimaryButton
|
||||
:class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing"
|
||||
v-text="transl('create.title')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
43
resources/js/Pages/Admin/Departments/Destroy.vue
Normal file
43
resources/js/Pages/Admin/Departments/Destroy.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
import { transl, goTo } from './Component'
|
||||
import { router } from '@inertiajs/vue3';
|
||||
|
||||
import DestroyModal from '@/Components/Dashboard/Modal/Destroy.vue';
|
||||
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
|
||||
|
||||
const emit = defineEmits([
|
||||
'close',
|
||||
'switchModal'
|
||||
]);
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
model: Object
|
||||
});
|
||||
|
||||
const destroy = (id) => router.delete(route(goTo('destroy'), {id}), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
props.model.pop;
|
||||
Notify.success(transl('deleted'));
|
||||
emit('close');
|
||||
},
|
||||
onError: () => {
|
||||
Notify.info(transl('notFound'));
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DestroyModal
|
||||
:show="show"
|
||||
@close="$emit('close')"
|
||||
@destroy="destroy(model.id)"
|
||||
>
|
||||
<Header
|
||||
:title="model.name"
|
||||
:subtitle="model.description"
|
||||
/>
|
||||
</DestroyModal>
|
||||
</template>
|
||||
67
resources/js/Pages/Admin/Departments/Edit.vue
Normal file
67
resources/js/Pages/Admin/Departments/Edit.vue
Normal file
@ -0,0 +1,67 @@
|
||||
<script setup>
|
||||
import { goTo } from './Component';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { onUpdated } from 'vue';
|
||||
|
||||
import Input from '@/Components/Dashboard/Form/Input.vue';
|
||||
import EditModal from '@/Components/Dashboard/Modal/Edit.vue';
|
||||
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
|
||||
|
||||
const emit = defineEmits([
|
||||
'close',
|
||||
'switchModal'
|
||||
]);
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
model: Object
|
||||
});
|
||||
|
||||
const form = useForm({});
|
||||
|
||||
const update = (id) => {
|
||||
form.transform(data => ({
|
||||
...props.model
|
||||
})).put(route(goTo('update'), {id}),{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
Notify.success(lang('updated'))
|
||||
emit('switchModal')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<EditModal
|
||||
:show="show"
|
||||
@close="$emit('close')"
|
||||
@update="update(model.id)"
|
||||
>
|
||||
<Header
|
||||
:title="model.name"
|
||||
/>
|
||||
<div class="py-2 border-b">
|
||||
<div class="p-4">
|
||||
<form>
|
||||
<div class="grid gap-6 mb-6 lg:grid-cols-2">
|
||||
<Input
|
||||
id="Nombre"
|
||||
placeholder="name"
|
||||
v-model="model.name"
|
||||
:onError="form.errors.name"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
id="Descripción"
|
||||
v-model="model.description"
|
||||
:onError="form.errors.description"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</EditModal>
|
||||
</template>
|
||||
138
resources/js/Pages/Admin/Departments/Index.vue
Normal file
138
resources/js/Pages/Admin/Departments/Index.vue
Normal file
@ -0,0 +1,138 @@
|
||||
<script setup>
|
||||
import { transl, can, goTo } from './Component'
|
||||
import { ref } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
|
||||
import ModalController from '@/Controllers/ModalController.js';
|
||||
import SearcherController from '@/Controllers/SearcherController.js';
|
||||
|
||||
import SearcherHead from '@/Components/Dashboard/Searcher.vue';
|
||||
import Table from '@/Components/Dashboard/Table.vue';
|
||||
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
|
||||
import DashboardLayout from '@/Layouts/DashboardLayout.vue';
|
||||
import DestroyView from './Destroy.vue';
|
||||
import EditView from './Edit.vue';
|
||||
import ShowView from './Show.vue';
|
||||
|
||||
const props = defineProps({
|
||||
departments: Object
|
||||
});
|
||||
|
||||
// Controladores
|
||||
const Modal = new ModalController();
|
||||
const Searcher = new SearcherController(goTo('index'));
|
||||
|
||||
// Variables de controladores
|
||||
const destroyModal = ref(Modal.destroyModal);
|
||||
const editModal = ref(Modal.editModal);
|
||||
const showModal = ref(Modal.showModal);
|
||||
const modelModal = ref(Modal.modelModal);
|
||||
const query = ref(Searcher.query);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout :title="transl('system')">
|
||||
<SearcherHead @search="Searcher.search">
|
||||
<Link
|
||||
v-if="can('create')"
|
||||
:href="route(goTo('create'))"
|
||||
>
|
||||
<GoogleIcon
|
||||
:title="$t('crud.create')"
|
||||
class="btn-icon-primary"
|
||||
name="add"
|
||||
outline
|
||||
/>
|
||||
</Link>
|
||||
</SearcherHead>
|
||||
<div class="pt-2 w-full">
|
||||
<Table
|
||||
:items="departments"
|
||||
@send-pagination="Searcher.searchWithPagination"
|
||||
>
|
||||
<template #head>
|
||||
<th
|
||||
class="table-item"
|
||||
v-text="$t('Nombre')"
|
||||
/>
|
||||
<th
|
||||
class="table-item"
|
||||
v-text="$t('Descripción')"
|
||||
/>
|
||||
<th
|
||||
class="table-item w-44"
|
||||
v-text="$t('actions')"
|
||||
/>
|
||||
</template>
|
||||
<template #body="{items}">
|
||||
<tr v-for="model in items">
|
||||
<td class="table-item border">
|
||||
<div class="flex items-center text-sm">
|
||||
<div>
|
||||
<p class="font-semibold">
|
||||
{{ model.name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-item border">
|
||||
<div class="flex items-center text-sm">
|
||||
<div>
|
||||
<p class="font-semibold">
|
||||
{{ model.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-item border">
|
||||
<div class="flex justify-center space-x-2">
|
||||
<GoogleIcon
|
||||
v-if="can('edit')"
|
||||
:title="$t('crud.edit')"
|
||||
class="btn-icon-primary"
|
||||
name="edit"
|
||||
outline
|
||||
@click="Modal.switchEditModal(model)"
|
||||
/>
|
||||
<GoogleIcon
|
||||
v-if="can('destroy')"
|
||||
:title="$t('crud.destroy')"
|
||||
class="btn-icon-primary"
|
||||
name="delete"
|
||||
outline
|
||||
@click="Modal.switchDestroyModal(model)"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template #empty>
|
||||
<td class="table-item border">
|
||||
<div class="flex items-center text-sm">
|
||||
<div>
|
||||
<p class="font-semibold">
|
||||
{{ $t('registers.empty') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-item border">-</td>
|
||||
<td class="table-item border">-</td>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
<EditView
|
||||
v-if="can('edit')"
|
||||
:show="editModal"
|
||||
:model="modelModal"
|
||||
@switchModal="Modal.switchShowEditModal"
|
||||
@close="Modal.switchEditModal"
|
||||
/>
|
||||
<DestroyView
|
||||
v-if="can('create')"
|
||||
:show="destroyModal"
|
||||
:model="modelModal"
|
||||
@close="Modal.switchDestroyModal"
|
||||
/>
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
41
resources/js/Pages/Admin/Departments/Show.vue
Normal file
41
resources/js/Pages/Admin/Departments/Show.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
import { can } from './Component'
|
||||
|
||||
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
|
||||
import ShowModal from '@/Components/Dashboard/Modal/Show.vue';
|
||||
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
|
||||
|
||||
defineEmits([
|
||||
'close',
|
||||
'switchModal'
|
||||
]);
|
||||
|
||||
defineProps({
|
||||
show: Boolean,
|
||||
model: Object
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<ShowModal
|
||||
:show="show"
|
||||
:editable="can('edit')"
|
||||
@close="$emit('close')"
|
||||
@edit="$emit('switchModal')"
|
||||
editable
|
||||
>
|
||||
<Header
|
||||
:title="model.alias"
|
||||
>
|
||||
</Header>
|
||||
<div class="py-2 border-b">
|
||||
<div class="px-4 py-2 flex">
|
||||
<GoogleIcon
|
||||
class="text-xl text-success"
|
||||
name="contact_mail"
|
||||
/>
|
||||
<div class="pl-3">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ShowModal>
|
||||
</template>
|
||||
15
resources/js/Pages/Admin/MainRole/Component.js
Normal file
15
resources/js/Pages/Admin/MainRole/Component.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { t } from '@/Lang/i18n';
|
||||
import { hasPermission } from '@/rolePermission.js';
|
||||
|
||||
// Obtener ruta
|
||||
const goTo = (route) => `admin.mainRoles.${route}`
|
||||
// Obtener traducción del componente
|
||||
const transl = (lang) => t(`mainRole.${lang}`)
|
||||
// Determina si un usuario puede hacer algo no en base a los permisos
|
||||
const can = (permission) => hasPermission(`users.${permission}`)
|
||||
|
||||
export {
|
||||
can,
|
||||
goTo,
|
||||
transl
|
||||
}
|
||||
89
resources/js/Pages/Admin/MainRole/Create.vue
Normal file
89
resources/js/Pages/Admin/MainRole/Create.vue
Normal file
@ -0,0 +1,89 @@
|
||||
<script setup>
|
||||
import { goTo, transl } from "./Component";
|
||||
import { Link, useForm } from "@inertiajs/vue3";
|
||||
|
||||
import PrimaryButton from "@/Components/Dashboard/Button/Primary.vue";
|
||||
import Input from "@/Components/Dashboard/Form/Input.vue";
|
||||
import PageHeader from "@/Components/Dashboard/PageHeader.vue";
|
||||
import GoogleIcon from "@/Components/Shared/GoogleIcon.vue";
|
||||
import DashboardLayout from "@/Layouts/DashboardLayout.vue";
|
||||
import Selectable from "@/Components/Dashboard/Form/Selectable.vue";
|
||||
|
||||
defineProps({
|
||||
departments: Object,
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
name: "",
|
||||
description: "",
|
||||
department_id: "",
|
||||
});
|
||||
|
||||
const submit = () =>
|
||||
form
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
department_id: form.department_id?.id,
|
||||
}))
|
||||
.post(route(goTo("store")), {
|
||||
onSuccess: () => Notify.success(transl("create.onSuccess")),
|
||||
onError: () => Notify.error(transl("create.onError")),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout :title="transl('create.title')">
|
||||
<PageHeader>
|
||||
<Link :href="route(goTo('index'))">
|
||||
<GoogleIcon
|
||||
:title="$t('return')"
|
||||
class="btn-icon-primary"
|
||||
name="arrow_back"
|
||||
outline
|
||||
/>
|
||||
</Link>
|
||||
</PageHeader>
|
||||
<div class="w-full pb-8">
|
||||
<div class="mt-8">
|
||||
<p v-text="transl('create.description')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<form @submit.prevent="submit" class="grid gap-4 grid-cols-6">
|
||||
<Input
|
||||
id="Nombre"
|
||||
class="col-span-2"
|
||||
v-model="form.name"
|
||||
:onError="form.errors.name"
|
||||
autofocus
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
id="Descrición"
|
||||
class="col-span-2"
|
||||
v-model="form.description"
|
||||
:onError="form.errors.description"
|
||||
autofocus
|
||||
/>
|
||||
<Selectable
|
||||
id="department_id"
|
||||
class="col-span-3"
|
||||
v-model="form.department_id"
|
||||
:options="departments"
|
||||
:onError="form.errors.department_id"
|
||||
:title="$t('department.title')"
|
||||
required
|
||||
/>
|
||||
<div
|
||||
class="col-span-6 flex flex-col items-center justify-end space-y-4 mt-4"
|
||||
>
|
||||
<PrimaryButton
|
||||
:class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing"
|
||||
v-text="transl('create.title')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
43
resources/js/Pages/Admin/MainRole/Destroy.vue
Normal file
43
resources/js/Pages/Admin/MainRole/Destroy.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
import { transl, goTo } from './Component'
|
||||
import { router } from '@inertiajs/vue3';
|
||||
|
||||
import DestroyModal from '@/Components/Dashboard/Modal/Destroy.vue';
|
||||
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
|
||||
|
||||
const emit = defineEmits([
|
||||
'close',
|
||||
'switchModal'
|
||||
]);
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
model: Object
|
||||
});
|
||||
|
||||
const destroy = (id) => router.delete(route(goTo('destroy'), {id}), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
props.model.pop;
|
||||
Notify.success(transl('deleted'));
|
||||
emit('close');
|
||||
},
|
||||
onError: () => {
|
||||
Notify.info(transl('notFound'));
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DestroyModal
|
||||
:show="show"
|
||||
@close="$emit('close')"
|
||||
@destroy="destroy(model.id)"
|
||||
>
|
||||
<Header
|
||||
:title="model.name"
|
||||
:subtitle="model.description"
|
||||
/>
|
||||
</DestroyModal>
|
||||
</template>
|
||||
67
resources/js/Pages/Admin/MainRole/Edit.vue
Normal file
67
resources/js/Pages/Admin/MainRole/Edit.vue
Normal file
@ -0,0 +1,67 @@
|
||||
<script setup>
|
||||
import { goTo } from './Component';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { onUpdated } from 'vue';
|
||||
|
||||
import Input from '@/Components/Dashboard/Form/Input.vue';
|
||||
import EditModal from '@/Components/Dashboard/Modal/Edit.vue';
|
||||
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
|
||||
|
||||
const emit = defineEmits([
|
||||
'close',
|
||||
'switchModal'
|
||||
]);
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
model: Object
|
||||
});
|
||||
|
||||
const form = useForm({});
|
||||
|
||||
const update = (id) => {
|
||||
form.transform(data => ({
|
||||
...props.model
|
||||
})).put(route(goTo('update'), {id}),{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
Notify.success(lang('updated'))
|
||||
emit('switchModal')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<EditModal
|
||||
:show="show"
|
||||
@close="$emit('close')"
|
||||
@update="update(model.id)"
|
||||
>
|
||||
<Header
|
||||
:title="model.name"
|
||||
/>
|
||||
<div class="py-2 border-b">
|
||||
<div class="p-4">
|
||||
<form>
|
||||
<div class="grid gap-6 mb-6 lg:grid-cols-2">
|
||||
<Input
|
||||
id="Nombre"
|
||||
placeholder="name"
|
||||
v-model="model.name"
|
||||
:onError="form.errors.name"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
id="Descripción"
|
||||
v-model="model.description"
|
||||
:onError="form.errors.description"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</EditModal>
|
||||
</template>
|
||||
151
resources/js/Pages/Admin/MainRole/Index.vue
Normal file
151
resources/js/Pages/Admin/MainRole/Index.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<script setup>
|
||||
import { transl, can, goTo } from './Component'
|
||||
import { ref } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
|
||||
import ModalController from '@/Controllers/ModalController.js';
|
||||
import SearcherController from '@/Controllers/SearcherController.js';
|
||||
|
||||
import SearcherHead from '@/Components/Dashboard/Searcher.vue';
|
||||
import Table from '@/Components/Dashboard/Table.vue';
|
||||
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
|
||||
import DashboardLayout from '@/Layouts/DashboardLayout.vue';
|
||||
import DestroyView from './Destroy.vue';
|
||||
import EditView from './Edit.vue';
|
||||
import ShowView from './Show.vue';
|
||||
|
||||
const props = defineProps({
|
||||
mainRoles: Object
|
||||
});
|
||||
|
||||
// Controladores
|
||||
const Modal = new ModalController();
|
||||
const Searcher = new SearcherController(goTo('index'));
|
||||
|
||||
// Variables de controladores
|
||||
const destroyModal = ref(Modal.destroyModal);
|
||||
const editModal = ref(Modal.editModal);
|
||||
const showModal = ref(Modal.showModal);
|
||||
const modelModal = ref(Modal.modelModal);
|
||||
const query = ref(Searcher.query);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout :title="transl('system')">
|
||||
<SearcherHead @search="Searcher.search">
|
||||
<Link
|
||||
v-if="can('create')"
|
||||
:href="route(goTo('create'))"
|
||||
>
|
||||
<GoogleIcon
|
||||
:title="$t('crud.create')"
|
||||
class="btn-icon-primary"
|
||||
name="add"
|
||||
outline
|
||||
/>
|
||||
</Link>
|
||||
</SearcherHead>
|
||||
<div class="pt-2 w-full">
|
||||
<Table
|
||||
:items="mainRoles"
|
||||
@send-pagination="Searcher.searchWithPagination"
|
||||
>
|
||||
<template #head>
|
||||
<th
|
||||
class="table-item"
|
||||
v-text="$t('Nombre')"
|
||||
/>
|
||||
<th
|
||||
class="table-item"
|
||||
v-text="$t('Descripción')"
|
||||
/>
|
||||
<th
|
||||
class="table-item"
|
||||
v-text="$t('Departamento')"
|
||||
/>
|
||||
<th
|
||||
class="table-item w-44"
|
||||
v-text="$t('actions')"
|
||||
/>
|
||||
</template>
|
||||
<template #body="{items}">
|
||||
<tr v-for="model in items">
|
||||
<td class="table-item border">
|
||||
<div class="flex items-center text-sm">
|
||||
<div>
|
||||
<p class="font-semibold">
|
||||
{{ model.name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-item border">
|
||||
<div class="flex items-center text-sm">
|
||||
<div>
|
||||
<p class="font-semibold">
|
||||
{{ model.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-item border">
|
||||
<div class="flex items-center text-sm">
|
||||
<div>
|
||||
<p class="font-semibold">
|
||||
{{ model.department?.name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-item border">
|
||||
<div class="flex justify-center space-x-2">
|
||||
<GoogleIcon
|
||||
v-if="can('edit')"
|
||||
:title="$t('crud.edit')"
|
||||
class="btn-icon-primary"
|
||||
name="edit"
|
||||
outline
|
||||
@click="Modal.switchEditModal(model)"
|
||||
/>
|
||||
<GoogleIcon
|
||||
v-if="can('destroy')"
|
||||
:title="$t('crud.destroy')"
|
||||
class="btn-icon-primary"
|
||||
name="delete"
|
||||
outline
|
||||
@click="Modal.switchDestroyModal(model)"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template #empty>
|
||||
<td class="table-item border">
|
||||
<div class="flex items-center text-sm">
|
||||
<div>
|
||||
<p class="font-semibold">
|
||||
{{ $t('registers.empty') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="table-item border">-</td>
|
||||
<td class="table-item border">-</td>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
<EditView
|
||||
v-if="can('edit')"
|
||||
:show="editModal"
|
||||
:model="modelModal"
|
||||
@switchModal="Modal.switchShowEditModal"
|
||||
@close="Modal.switchEditModal"
|
||||
/>
|
||||
<DestroyView
|
||||
v-if="can('create')"
|
||||
:show="destroyModal"
|
||||
:model="modelModal"
|
||||
@close="Modal.switchDestroyModal"
|
||||
/>
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
41
resources/js/Pages/Admin/MainRole/Show.vue
Normal file
41
resources/js/Pages/Admin/MainRole/Show.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
import { can } from './Component'
|
||||
|
||||
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
|
||||
import ShowModal from '@/Components/Dashboard/Modal/Show.vue';
|
||||
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
|
||||
|
||||
defineEmits([
|
||||
'close',
|
||||
'switchModal'
|
||||
]);
|
||||
|
||||
defineProps({
|
||||
show: Boolean,
|
||||
model: Object
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<ShowModal
|
||||
:show="show"
|
||||
:editable="can('edit')"
|
||||
@close="$emit('close')"
|
||||
@edit="$emit('switchModal')"
|
||||
editable
|
||||
>
|
||||
<Header
|
||||
:title="model.alias"
|
||||
>
|
||||
</Header>
|
||||
<div class="py-2 border-b">
|
||||
<div class="px-4 py-2 flex">
|
||||
<GoogleIcon
|
||||
class="text-xl text-success"
|
||||
name="contact_mail"
|
||||
/>
|
||||
<div class="pl-3">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ShowModal>
|
||||
</template>
|
||||
@ -1,23 +1,26 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Admin\ScoreController;
|
||||
use App\Http\Controllers\Admin\UserController;
|
||||
use App\Http\Controllers\Dashboard\HistoryLogController;
|
||||
use App\Http\Controllers\Dashboard\IndexController;
|
||||
use App\Http\Controllers\Dashboard\NotificationController;
|
||||
use App\Http\Controllers\Admin\DepartmentController;
|
||||
use App\Http\Controllers\Admin\MainRoleController;
|
||||
use App\Http\Controllers\Developer\RoleController;
|
||||
use App\Http\Controllers\Example\IndexController as ExampleIndexController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/**
|
||||
* Rutas generales/publicas
|
||||
*
|
||||
*
|
||||
* Rutas accesibles por todos los usuarios y no usuarios
|
||||
*/
|
||||
Route::redirect('/', '/login');
|
||||
|
||||
/**
|
||||
* Rutas del Dashboard
|
||||
*
|
||||
*
|
||||
* El dashboard es el panel de los usuarios de forma general
|
||||
*/
|
||||
Route::prefix('dashboard')->name('dashboard.')->middleware([
|
||||
@ -44,7 +47,7 @@
|
||||
|
||||
/**
|
||||
* Rutas de administrador
|
||||
*
|
||||
*
|
||||
* Estas ubicaciones son del administrador, sin embargo el desarrollador
|
||||
* puede acceder a ellas.
|
||||
*/
|
||||
@ -52,7 +55,10 @@
|
||||
'auth:sanctum',
|
||||
config('jetstream.auth_session')
|
||||
])->group(function () {
|
||||
Route::resource('users', UserController::class);
|
||||
Route::resource('users', UserController::class);
|
||||
Route::resource('scores', ScoreController::class);
|
||||
Route::resource('departments', DepartmentController::class);
|
||||
Route::resource('mainRoles', MainRoleController::class);
|
||||
|
||||
Route::prefix('/users')->name('users.')->group(function()
|
||||
{
|
||||
@ -64,7 +70,7 @@
|
||||
|
||||
/**
|
||||
* Rutas solo del desarrollador
|
||||
*
|
||||
*
|
||||
* Son ubicaciones o funciones que pueden llegar a ser muy sensibles en el sistema, por lo que
|
||||
* solo el desarrollador debe de ser capaz de modificarlas o actualizarlas.
|
||||
*/
|
||||
@ -77,9 +83,9 @@
|
||||
|
||||
/**
|
||||
* Elementos de la plantilla
|
||||
*
|
||||
*
|
||||
* Estos son elementos que existen y pueden ser usados en la plantilla, vienen ejemplos de uso.
|
||||
*
|
||||
*
|
||||
* Estas rutas pueden ser comentadas o eliminadas cuando se finalice un proyecto. Por default estan ocultas
|
||||
* en el dashboard.
|
||||
*/
|
||||
@ -89,4 +95,4 @@
|
||||
config('jetstream.auth_session')
|
||||
])->group(function () {
|
||||
Route::get('/', [ExampleIndexController::class, 'index'])->name('index');
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user