Habilidades Puntuadas (#2)
Co-authored-by: Juan Felipe Zapata Moreno <zapata_pipe@hotmail.com> Reviewed-on: #2 Co-authored-by: Juan Felipe Zapata Moreno <juan.zapata@golsystems.com.mx> Co-committed-by: Juan Felipe Zapata Moreno <juan.zapata@golsystems.com.mx>
This commit is contained in:
parent
0a526df665
commit
023fd8d2f8
4
app/Http/Controllers/.prettierrc
Normal file
4
app/Http/Controllers/.prettierrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false
|
||||||
|
}
|
||||||
151
app/Http/Controllers/Admin/MainRoleSkillsController.php
Normal file
151
app/Http/Controllers/Admin/MainRoleSkillsController.php
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use App\Http\Requests\StoreMainRoleSkills;
|
||||||
|
use App\Http\Requests\UpdateMainRoleSkills;
|
||||||
|
use App\Models\department;
|
||||||
|
use App\Models\mainRole;
|
||||||
|
use App\Models\MainRoleSkills;
|
||||||
|
use App\Models\Score;
|
||||||
|
use App\Models\Skill;
|
||||||
|
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 MainRoleSkillsController extends VueController
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
return $this->vueRoot('admin.mainRoleSkills');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$q = request()->get('q');
|
||||||
|
|
||||||
|
$mainRole = mainRole::where('name', 'LIKE', "%{$q}%")->pluck('id');
|
||||||
|
$skills = Skill::where('name', 'LIKE', "%{$q}%")->pluck('id');
|
||||||
|
$scores = Score::where('alias', 'LIKE', "%{$q}%")->pluck('id');
|
||||||
|
|
||||||
|
$mainRoleSkills = MainRoleSkills::whereIn('main_role_id', $mainRole)
|
||||||
|
->orWhereIn('skill_id', $skills)
|
||||||
|
->orWhereIn('scored_id', $scores)
|
||||||
|
->with([
|
||||||
|
'mainRole:id,name,department_id',
|
||||||
|
'mainRole.department:id,name',
|
||||||
|
'skill:id,name',
|
||||||
|
'score:id,alias'
|
||||||
|
])
|
||||||
|
->paginate(config('app.pagination'));
|
||||||
|
|
||||||
|
$departments = department::select(['id', 'name', 'description'])->orderBy('name', 'ASC')->get();
|
||||||
|
$mainRoles = mainRole::with(['department:id,name'])->orderBy('name', 'ASC')->get();
|
||||||
|
$skills = Skill::with(['department:id,name'])->orderBy('name', 'ASC')->get();
|
||||||
|
$scores = Score::orderBy('alias', 'ASC')->get();
|
||||||
|
|
||||||
|
|
||||||
|
return $this->vuew('index', [
|
||||||
|
'mainRoleSkills' => $mainRoleSkills,
|
||||||
|
'departments' => $departments,
|
||||||
|
'mainRoles' => $mainRoles,
|
||||||
|
'skills' => $skills,
|
||||||
|
'scores' => $scores,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$roleId = request()->get('role_id');
|
||||||
|
$departmentId = request()->get('department_id');
|
||||||
|
|
||||||
|
// Obtener el rol seleccionado y su departamento
|
||||||
|
$selectedRole = null;
|
||||||
|
$selectedDepartment = null;
|
||||||
|
|
||||||
|
if ($roleId) {
|
||||||
|
$selectedRole = mainRole::with('department:id,name')->find($roleId);
|
||||||
|
if ($selectedRole && $selectedRole->department) {
|
||||||
|
$selectedDepartment = $selectedRole->department;
|
||||||
|
}
|
||||||
|
} elseif ($departmentId) {
|
||||||
|
$selectedDepartment = department::find($departmentId);
|
||||||
|
}
|
||||||
|
$mainRoles = mainRole::with('department:id,name')->orderBy('name', 'ASC')->get();
|
||||||
|
$skills = Skill::with('department:id,name')->orderBy('name', 'ASC')->get();
|
||||||
|
$scores = Score::orderBy('alias', 'ASC')->get();
|
||||||
|
|
||||||
|
return $this->vuew('create', [
|
||||||
|
'mainRoles' => $mainRoles,
|
||||||
|
'skills' => $skills,
|
||||||
|
'scores' => $scores,
|
||||||
|
'selectedRole' => $selectedRole,
|
||||||
|
'selectedDepartment' => $selectedDepartment
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(StoreMainRoleSkills $request)
|
||||||
|
{
|
||||||
|
$create = [];
|
||||||
|
foreach ($request['skills'] as $skill) {
|
||||||
|
$create[] = [
|
||||||
|
'main_role_id' => $request['main_role_id'],
|
||||||
|
'skill_id' => $skill['skill_id'],
|
||||||
|
'scored_id' => $skill['scored_id'],
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
MainRoleSkills::insert($create);
|
||||||
|
|
||||||
|
return $this->index();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(MainRoleSkills $mainRoleSkills)
|
||||||
|
{
|
||||||
|
$mainRoleSkills->load(['mainRole.department', 'skill.department', 'score']);
|
||||||
|
$scores = Score::orderBy('alias', 'ASC')->get();
|
||||||
|
|
||||||
|
return $this->vuew('edit', [
|
||||||
|
'mainRoleSkills' => $mainRoleSkills,
|
||||||
|
'scores' => $scores,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function update(UpdateMainRoleSkills $request, $id)
|
||||||
|
{
|
||||||
|
$mainRoleSkills = MainRoleSkills::findOrFail($id);
|
||||||
|
|
||||||
|
$mainRoleSkills->update([
|
||||||
|
'scored_id' => $request->scored_id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$mainRoleSkills = MainRoleSkills::findOrFail($id);
|
||||||
|
|
||||||
|
$mainRoleSkills->delete();
|
||||||
|
|
||||||
|
return $this->index();
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error($th->getMessage());
|
||||||
|
return response()->json(['error' => 'Error al eliminar las habilidades']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/Http/Controllers/Admin/SkillController.php
Normal file
65
app/Http/Controllers/Admin/SkillController.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php namespace App\Http\Controllers\Admin;
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use App\Http\Requests\StoreSkill;
|
||||||
|
use App\Http\Requests\UpdateSkill;
|
||||||
|
use App\Models\department;
|
||||||
|
use App\Models\Skill;
|
||||||
|
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 SkillController extends VueController
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
return $this->vueRoot('admin.skills');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$q = request()->get('q');
|
||||||
|
|
||||||
|
$skills = Skill::orderBy('name', 'ASC')
|
||||||
|
->where('name', 'LIKE', "%{$q}%")
|
||||||
|
->with('department:id,name')
|
||||||
|
->paginate(config('app.pagination'));
|
||||||
|
|
||||||
|
return $this->vuew('index', [
|
||||||
|
'skills' => $skills,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$department = department::orderBy('name', 'ASC')->get();
|
||||||
|
|
||||||
|
return $this->vuew('create', [
|
||||||
|
'departments' => $department,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(StoreSkill $request)
|
||||||
|
{
|
||||||
|
Skill::create($request->all());
|
||||||
|
|
||||||
|
return $this->index();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(UpdateSkill $request, Skill $skill)
|
||||||
|
{
|
||||||
|
$skill->update($request->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy (Skill $skill)
|
||||||
|
{
|
||||||
|
$skill->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
41
app/Http/Requests/StoreMainRoleSkills.php
Normal file
41
app/Http/Requests/StoreMainRoleSkills.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valida el almacenamiento de un usuario
|
||||||
|
*
|
||||||
|
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
|
||||||
|
*
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
class StoreMainRoleSkills 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 [
|
||||||
|
'main_role_id' => ['required', 'integer'],
|
||||||
|
'skills' => ['required', 'array', 'min:1'],
|
||||||
|
'skills.*.skill_id' => ['required', 'integer'],
|
||||||
|
'skills.*.scored_id' => ['required', 'integer'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Http/Requests/StoreSkill.php
Normal file
40
app/Http/Requests/StoreSkill.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 StoreSkill 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'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/Http/Requests/UpdateMainRoleSkills.php
Normal file
38
app/Http/Requests/UpdateMainRoleSkills.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?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 UpdateMainRoleSkills 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 [
|
||||||
|
'scored_id' => ['required', 'integer'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Http/Requests/UpdateSkill.php
Normal file
40
app/Http/Requests/UpdateSkill.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 UpdateSkill 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'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
45
app/Models/MainRoleSkills.php
Normal file
45
app/Models/MainRoleSkills.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?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 MainRoleSkills extends Model
|
||||||
|
{
|
||||||
|
use HasFactory,
|
||||||
|
ModelExtend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atributos llenables masivamente
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'main_role_id',
|
||||||
|
'skill_id',
|
||||||
|
'scored_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mainRole()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(mainRole::class, 'main_role_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function skill()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Skill::class, 'skill_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function score()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Score::class, 'scored_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,4 +27,9 @@ class Score extends Model
|
|||||||
'value',
|
'value',
|
||||||
'description',
|
'description',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function mainRoleSkills()
|
||||||
|
{
|
||||||
|
return $this->hasMany(MainRoleSkills::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
app/Models/Skill.php
Normal file
46
app/Models/Skill.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?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 Skill extends Model
|
||||||
|
{
|
||||||
|
use HasFactory,
|
||||||
|
ModelExtend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atributos llenables masivamente
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'department_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function department()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(department::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mainRoles()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(MainRole::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mainRoleSkills()
|
||||||
|
{
|
||||||
|
return $this->hasMany(MainRoleSkills::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -34,4 +34,9 @@ public function mainRoles()
|
|||||||
{
|
{
|
||||||
return $this->hasMany(MainRole::class);
|
return $this->hasMany(MainRole::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function skills()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Skill::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ class MainRole extends Model
|
|||||||
/**
|
/**
|
||||||
* Nombre de la tabla
|
* Nombre de la tabla
|
||||||
*/
|
*/
|
||||||
protected $table = 'main_role';
|
protected $table = 'main_roles';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Atributos llenables masivamente
|
* Atributos llenables masivamente
|
||||||
@ -40,4 +40,14 @@ public function department()
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(department::class);
|
return $this->belongsTo(department::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function skills()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Skill::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mainRoleSkills()
|
||||||
|
{
|
||||||
|
return $this->hasMany(MainRoleSkills::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('main_role', function (Blueprint $table) {
|
Schema::create('main_roles', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->string('description')->nullable();
|
$table->string('description')->nullable();
|
||||||
@ -25,6 +25,6 @@ public function up(): void
|
|||||||
*/
|
*/
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::dropIfExists('main_role');
|
Schema::dropIfExists('main_roles');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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('skills', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('description');
|
||||||
|
$table->foreignId('department_id')->constrained('departments')->onDelete('cascade');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('skills');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
<?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_skills', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('main_role_id')->constrained('main_roles')->onDelete('cascade');
|
||||||
|
$table->foreignId('skill_id')->constrained('skills')->onDelete('cascade');
|
||||||
|
$table->foreignId('scored_id')->constrained('scores')->onDelete('cascade');
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->unique(['main_role_id', 'skill_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('main_role_skills');
|
||||||
|
}
|
||||||
|
};
|
||||||
32
resources/js/Components/App/DepartmentSelector.vue
Normal file
32
resources/js/Components/App/DepartmentSelector.vue
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<script setup>
|
||||||
|
defineEmits(['select']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
departments: Object,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="mb-6 mt-12">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">{{ $t('department.select.title') }}</h2>
|
||||||
|
<p class="text-gray-600">{{ $t('department.select.description') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
<button
|
||||||
|
v-for="department in departments"
|
||||||
|
:key="department.id"
|
||||||
|
@click="$emit('select', department)"
|
||||||
|
class="group p-6 border-2 border-gray-200 rounded-lg hover:border-primary hover:bg-primary/5 transition-all duration-200 text-left focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
<h3 class="font-semibold text-lg mb-2 group-hover:text-primary transition-colors">
|
||||||
|
{{ department.name }}
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 text-sm">
|
||||||
|
{{ department.description }}
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
47
resources/js/Components/App/MainRoleSelector.vue
Normal file
47
resources/js/Components/App/MainRoleSelector.vue
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(['select']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
mainRoles: Object,
|
||||||
|
selectedDepartment: Object,
|
||||||
|
});
|
||||||
|
|
||||||
|
const departmentMainRoles = computed(() => {
|
||||||
|
return props.mainRoles.filter(role =>
|
||||||
|
role.department && role.department.id === props.selectedDepartment.id
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="mb-6 mt-12">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">
|
||||||
|
{{ $t('mainRole.inDepartment') }}
|
||||||
|
</h2>
|
||||||
|
<p class="text-gray-600">{{ $t('mainRole.select.description') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="departmentMainRoles.length > 0" class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<button
|
||||||
|
v-for="mainRole in departmentMainRoles"
|
||||||
|
:key="mainRole.id"
|
||||||
|
@click="$emit('select', mainRole)"
|
||||||
|
class="group p-6 border-2 border-gray-200 rounded-lg hover:border-primary hover:bg-primary/5 transition-all duration-200 text-left focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
<h3 class="font-semibold text-lg mb-2 group-hover:text-primary transition-colors">
|
||||||
|
{{ mainRole.name }}
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 text-sm">
|
||||||
|
{{ mainRole.description}}
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="text-center py-12">
|
||||||
|
<p class="text-gray-500 text-lg">{{ $t('mainRole.noRolesInDepartment') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
177
resources/js/Components/App/SkillAssignment.vue
Normal file
177
resources/js/Components/App/SkillAssignment.vue
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import PrimaryButton from "@/Components/Dashboard/Button/Primary.vue";
|
||||||
|
import Selectable from "@/Components/Dashboard/Form/Selectable.vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(["submit"]);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
form: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
skills: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
scores: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
selectedDepartment: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
selectedMainRole: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const skillId = ref("");
|
||||||
|
const scoredId = ref("");
|
||||||
|
const todos = ref([]);
|
||||||
|
|
||||||
|
function addTodo() {
|
||||||
|
if (skillId.value && scoredId.value) {
|
||||||
|
const selectedSkill = props.skills.find(
|
||||||
|
(skill) => skill.id === (skillId.value.id || skillId.value)
|
||||||
|
);
|
||||||
|
const selectedScore = props.scores.find(
|
||||||
|
(score) => score.id === (scoredId.value.id || scoredId.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selectedSkill && selectedScore) {
|
||||||
|
// Verificar que no esté duplicada
|
||||||
|
const isDuplicate = todos.value.some(
|
||||||
|
(todo) => todo.skill_id === selectedSkill.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDuplicate) {
|
||||||
|
const newItem = {
|
||||||
|
skill_id: selectedSkill.id,
|
||||||
|
scored_id: selectedScore.id,
|
||||||
|
skill_name: selectedSkill.name,
|
||||||
|
score_alias: selectedScore.alias,
|
||||||
|
};
|
||||||
|
|
||||||
|
todos.value.push(newItem);
|
||||||
|
|
||||||
|
// Limpiar campos
|
||||||
|
skillId.value = "";
|
||||||
|
scoredId.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTodo(index) {
|
||||||
|
todos.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFormValid = computed(() => {
|
||||||
|
return todos.value.length > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const submitForm = () => {
|
||||||
|
if (isFormValid.value) {
|
||||||
|
emit("submit", todos.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="mb-6">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">
|
||||||
|
Asignar Habilidades al Rol: {{ selectedMainRole.name }}
|
||||||
|
</h2>
|
||||||
|
<p class="text-gray-600">Departamento: {{ selectedDepartment.name }}</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="form.errors.skills"
|
||||||
|
class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4 mt-4"
|
||||||
|
>
|
||||||
|
{{ form.errors.skills }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formulario para agregar habilidades -->
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border p-6 mb-6">
|
||||||
|
<div class="grid gap-6 grid-cols-1 md:grid-cols-2">
|
||||||
|
<Selectable
|
||||||
|
id="skill_id"
|
||||||
|
v-model="skillId"
|
||||||
|
:options="skills"
|
||||||
|
:title="$t('skill.title')"
|
||||||
|
placeholder="Selecciona una habilidad..."
|
||||||
|
label="name"
|
||||||
|
track-by="id"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Selectable
|
||||||
|
id="scored_id"
|
||||||
|
v-model="scoredId"
|
||||||
|
:options="scores"
|
||||||
|
:title="$t('scores.title')"
|
||||||
|
placeholder="Selecciona una puntuación..."
|
||||||
|
label="alias"
|
||||||
|
track-by="id"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center mt-6">
|
||||||
|
<button
|
||||||
|
@click="addTodo"
|
||||||
|
type="button"
|
||||||
|
:disabled="!skillId || !scoredId"
|
||||||
|
class="rounded-lg px-4 py-2 bg-primary text-white border disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Agregar Habilidad
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lista de habilidades agregadas -->
|
||||||
|
<div class="space-y-3 mb-6">
|
||||||
|
<template v-for="(todo, index) in todos" :key="index">
|
||||||
|
<div
|
||||||
|
class="flex bg-white items-center justify-between rounded-lg px-4 py-3 border shadow-sm"
|
||||||
|
>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="font-bold text-black">{{ todo.skill_name }}</div>
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
Puntuación: {{ todo.score_alias }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click="removeTodo(index)"
|
||||||
|
class="rounded-lg px-4 py-2 bg-red-600 text-white hover:bg-red-700 transition-colors"
|
||||||
|
>
|
||||||
|
Quitar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="todos.length === 0"
|
||||||
|
class="text-gray-500 text-center py-8 bg-gray-50 rounded-lg"
|
||||||
|
>
|
||||||
|
No hay habilidades agregadas
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Botón de envío -->
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<PrimaryButton
|
||||||
|
:class="{ 'opacity-25': form.processing }"
|
||||||
|
:disabled="form.processing || !isFormValid"
|
||||||
|
type="button"
|
||||||
|
@click="submitForm"
|
||||||
|
>
|
||||||
|
<span>Crear Asignación</span>
|
||||||
|
</PrimaryButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
135
resources/js/Components/App/SkillsRole.vue
Normal file
135
resources/js/Components/App/SkillsRole.vue
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
import { Link } from "@inertiajs/vue3";
|
||||||
|
import { can, goTo } from "@/Pages/Admin/MainRoleSkills/Component";
|
||||||
|
|
||||||
|
import GoogleIcon from "@/Components/Shared/GoogleIcon.vue";
|
||||||
|
import ModalController from "@/Controllers/ModalController.js";
|
||||||
|
import DestroyView from "@/Pages/Admin/MainRoleSkills/Destroy.vue";
|
||||||
|
import EditView from '@/Pages/Admin/MainRoleSkills/Edit.vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(["select"]);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
mainRoleSkills: Array,
|
||||||
|
selectedRole: Object,
|
||||||
|
scores: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Controlador de modal
|
||||||
|
const Modal = new ModalController();
|
||||||
|
const destroyModal = ref(Modal.destroyModal);
|
||||||
|
const editModal = ref(Modal.editModal);
|
||||||
|
const modelModal = ref(Modal.modelModal);
|
||||||
|
|
||||||
|
const roleSkills = computed(() => {
|
||||||
|
if (!props.mainRoleSkills || !props.selectedRole) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return props.mainRoleSkills.filter(
|
||||||
|
(roleSkill) => roleSkill.main_role_id === props.selectedRole.id
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="mb-6 mt-12">
|
||||||
|
<div class="flex gap-6">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">
|
||||||
|
{{ $t("skill.assignment.title") }}
|
||||||
|
</h2>
|
||||||
|
<Link
|
||||||
|
v-if="can('create')"
|
||||||
|
:href="
|
||||||
|
route(goTo('create'), {
|
||||||
|
role_id: selectedRole.id,
|
||||||
|
department_id:
|
||||||
|
selectedRole.department?.id || selectedRole.department_id,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<GoogleIcon
|
||||||
|
:title="$t('crud.create')"
|
||||||
|
class="btn-icon-primary"
|
||||||
|
name="add"
|
||||||
|
outline
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600">
|
||||||
|
Habilidades asignadas al rol: {{ selectedRole.name }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="roleSkills.length > 0"
|
||||||
|
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="roleSkill in roleSkills"
|
||||||
|
:key="roleSkill.id"
|
||||||
|
class="bg-white p-4 rounded-lg shadow hover:shadow-lg transition-shadow duration-200 cursor-pointer border"
|
||||||
|
@click="emit('select', roleSkill)"
|
||||||
|
>
|
||||||
|
<h3 class="text-lg font-medium mb-2 text-blue-600">
|
||||||
|
{{ roleSkill.skill.name }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
<span class="font-semibold">Puntuación:</span>
|
||||||
|
{{ roleSkill.score.alias }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="roleSkill.skill.description"
|
||||||
|
class="text-xs text-gray-500 mt-2"
|
||||||
|
>
|
||||||
|
{{ roleSkill.skill.description }}
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center justify-end mt-4 gap-2">
|
||||||
|
<GoogleIcon
|
||||||
|
v-if="can('edit')"
|
||||||
|
:title="$t('crud.edit')"
|
||||||
|
class="btn-icon-danger w-6 h-6 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||||
|
name="edit"
|
||||||
|
@click.stop="Modal.switchEditModal(roleSkill)"
|
||||||
|
/>
|
||||||
|
<GoogleIcon
|
||||||
|
v-if="can('destroy')"
|
||||||
|
:title="$t('crud.destroy')"
|
||||||
|
class="btn-icon-danger w-6 h-6 bg-red-500 text-white rounded hover:bg-red-600"
|
||||||
|
name="delete"
|
||||||
|
@click.stop="Modal.switchDestroyModal(roleSkill)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="text-center py-12">
|
||||||
|
<p class="text-gray-500 text-lg">
|
||||||
|
No hay habilidades asignadas a este rol
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-400 text-sm mt-2">
|
||||||
|
Las habilidades se pueden asignar desde la sección de creación
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<EditView
|
||||||
|
v-if="can('edit')"
|
||||||
|
:show="editModal"
|
||||||
|
:model="modelModal"
|
||||||
|
:scores="scores"
|
||||||
|
@close="Modal.switchEditModal"
|
||||||
|
@switchModal="Modal.switchEditModal"
|
||||||
|
/>
|
||||||
|
<DestroyView
|
||||||
|
v-if="can('destroy')"
|
||||||
|
:show="destroyModal"
|
||||||
|
:model="modelModal"
|
||||||
|
@close="Modal.switchDestroyModal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
38
resources/js/Components/App/StepIndicator.vue
Normal file
38
resources/js/Components/App/StepIndicator.vue
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
currentStep: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{ number: 1, name: 'department.title' },
|
||||||
|
{ number: 2, name: 'mainRole.title' },
|
||||||
|
{ number: 3, name: 'skill.title' }
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full pb-4">
|
||||||
|
<div class="flex items-center justify-center space-x-4 mb-6">
|
||||||
|
<template v-for="(step, index) in steps" :key="step.number">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div
|
||||||
|
class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium transition-colors"
|
||||||
|
:class="currentStep >= step.number ? 'bg-primary text-primary-on' : 'bg-gray-300 text-gray-600'"
|
||||||
|
>
|
||||||
|
{{ step.number }}
|
||||||
|
</div>
|
||||||
|
<span class="ml-2 text-sm font-medium">{{ $t(step.name) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="index < steps.length - 1"
|
||||||
|
class="w-8 h-1 bg-gray-300 transition-colors"
|
||||||
|
:class="currentStep > step.number ? 'bg-primary' : ''"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -46,31 +46,31 @@ const onRemove = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col relative">
|
||||||
<label v-if="title" class="block mb-2 text-sm font-medium text-gray-900">
|
<label v-if="title" class="block mb-2 text-sm font-medium text-gray-900">
|
||||||
{{title}} <span class="text-red-500" v-if="required">*</span> <slot name="label-icon" />
|
{{title}} <span class="text-red-500" v-if="required">*</span> <slot name="label-icon" />
|
||||||
</label>
|
</label>
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
v-model="value"
|
v-model="value"
|
||||||
ref="multiselect"
|
ref="multiselect"
|
||||||
:options="options"
|
:options="options"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:close-on-select="true"
|
:close-on-select="true"
|
||||||
:clear-on-select="false"
|
:clear-on-select="false"
|
||||||
:preserve-search="true"
|
:preserve-search="true"
|
||||||
selectedLabel="Seleccionado"
|
selectedLabel="Seleccionado"
|
||||||
selectLabel="Seleccionar"
|
selectLabel="Seleccionar"
|
||||||
deselectLabel="Remover"
|
deselectLabel="Remover"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:label="label"
|
:label="label"
|
||||||
:track-by="trackBy"
|
:track-by="trackBy"
|
||||||
:required="required"
|
:required="required"
|
||||||
@select="onChange"
|
@select="onChange"
|
||||||
@remove="onRemove"
|
@remove="onRemove"
|
||||||
>
|
>
|
||||||
<template #noOptions>
|
<template #noOptions>
|
||||||
{{ $t('noRecords') }}
|
{{ $t('noRecords') }}
|
||||||
</template>
|
</template>
|
||||||
</VueMultiselect>
|
</VueMultiselect>
|
||||||
<p v-show="onError" class="text-sm text-red-600">
|
<p v-show="onError" class="text-sm text-red-600">
|
||||||
{{ onError }}
|
{{ onError }}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ const props = defineProps({
|
|||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="w-full right-0 mt-2">
|
<div class="w-full right-0 mt-2">
|
||||||
<div class="rounded overflow-hidden">
|
<div class="rounded">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,257 +1,318 @@
|
|||||||
export default {
|
export default {
|
||||||
'&':'y',
|
"&": "y",
|
||||||
account: {
|
account: {
|
||||||
delete: {
|
delete: {
|
||||||
confirm:'¿Está seguro de que quiere eliminar su cuenta? Una vez eliminada su cuenta, todos sus recursos y datos se borrarán permanentemente. Por favor, introduzca su contraseña para confirmar que desea eliminar permanentemente su cuenta.',
|
confirm:
|
||||||
description:'Eliminar permanentemente su cuenta.',
|
"¿Está seguro de que quiere eliminar su cuenta? Una vez eliminada su cuenta, todos sus recursos y datos se borrarán permanentemente. Por favor, introduzca su contraseña para confirmar que desea eliminar permanentemente su cuenta.",
|
||||||
onDelete:'Una vez eliminada su cuenta, todos sus recursos y datos se borrarán permanentemente. Antes de eliminar su cuenta, descargue los datos o la información que desee conservar.',
|
description: "Eliminar permanentemente su cuenta.",
|
||||||
title:'Eliminar cuenta',
|
onDelete:
|
||||||
},
|
"Una vez eliminada su cuenta, todos sus recursos y datos se borrarán permanentemente. Antes de eliminar su cuenta, descargue los datos o la información que desee conservar.",
|
||||||
email: {
|
title: "Eliminar cuenta",
|
||||||
notifySendVerification:'Se ha enviado un nuevo enlace de verificación a su dirección de correo electrónico.',
|
|
||||||
sendVerification:'Haga clic aquí para volver a enviar el correo electrónico de verificación.',
|
|
||||||
unverify: 'Su dirección de correo electrónico no está verificada.',
|
|
||||||
},
|
|
||||||
manage:'Administrar cuenta',
|
|
||||||
password: {
|
|
||||||
description:'Asegúrese de que su cuenta utiliza una contraseña larga y aleatoria para estar seguro.',
|
|
||||||
new:'Nueva contraseña',
|
|
||||||
reset:'Restaurar contraseña',
|
|
||||||
secure:'Esta es una zona segura de la aplicación. Confirme su contraseña antes de continuar.',
|
|
||||||
update: 'Actualizar contraseña',
|
|
||||||
verify:'Por su seguridad, confirme su contraseña para continuar.',
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
description:'Actualice la información del perfil de su cuenta y su dirección de correo electrónico.',
|
|
||||||
title:'Información del perfil',
|
|
||||||
},
|
|
||||||
sessions: {
|
|
||||||
confirm:'Por favor, introduzca su contraseña para confirmar que desea salir de sus otras sesiones de navegación en todos sus dispositivos.',
|
|
||||||
description: 'Gestiona y cierra tus sesiones activas en otros navegadores y dispositivos.',
|
|
||||||
last:'Último activo',
|
|
||||||
logout:'Cerrar otras sesiones del navegador',
|
|
||||||
onLogout:'Si es necesario, puede cerrar la sesión de todos sus otros navegadores en todos sus dispositivos. A continuación se enumeran algunas de sus sesiones recientes; sin embargo, esta lista puede no ser exhaustiva. Si crees que tu cuenta ha sido comprometida, también deberías actualizar tu contraseña.',
|
|
||||||
this: 'Dispositivo actual',
|
|
||||||
title: 'Sesiones del navegador',
|
|
||||||
},
|
|
||||||
twoFactor: {
|
|
||||||
codes:{
|
|
||||||
regenerate:'Regenerar los códigos de recuperación',
|
|
||||||
show:'Mostrar códigos de recuperación',
|
|
||||||
store:'Guarde estos códigos de recuperación en un gestor de contraseñas seguro. Pueden utilizarse para recuperar el acceso a su cuenta si se pierde su dispositivo de autenticación de dos factores.',
|
|
||||||
},
|
|
||||||
description:'Añada seguridad adicional a su cuenta mediante la autenticación de dos factores.',
|
|
||||||
isEnable:'Ha activado la autenticación de dos factores.',
|
|
||||||
isNotEnable:{
|
|
||||||
title:'No ha activado la autenticación de dos factores.',
|
|
||||||
description:'Cuando la autenticación de dos factores está activada, se le pedirá un token seguro y aleatorio durante la autenticación. Puedes recuperar este token desde la aplicación Google Authenticator de tu teléfono.',
|
|
||||||
},
|
|
||||||
key:'Llave de configuración',
|
|
||||||
login: {
|
|
||||||
onAuth: 'Por favor, confirme el acceso a su cuenta introduciendo el código de autentificación proporcionado por su aplicación de autentificación.',
|
|
||||||
onRecovery: 'Confirme el acceso a su cuenta introduciendo uno de sus códigos de recuperación de emergencia.',
|
|
||||||
},
|
|
||||||
onFinish:'Termina de habilitar la autenticación de dos factores.',
|
|
||||||
qr: {
|
|
||||||
isConfirmed: 'La autenticación de dos factores ya está activada. Escanee el siguiente código QR con la aplicación de autenticación de su teléfono o introduzca la clave de configuración.',
|
|
||||||
onConfirmed: 'Para terminar de habilitar la autenticación de dos factores, escanea el siguiente código QR utilizando la aplicación de autenticación de tu teléfono o introduce la clave de configuración y proporciona el código OTP generado.',
|
|
||||||
},
|
|
||||||
recovery: {
|
|
||||||
code: 'Código de recuperación',
|
|
||||||
useAuth: 'Utilizar un código de autentificación',
|
|
||||||
useCode: 'Utiliza un código de recuperación',
|
|
||||||
},
|
|
||||||
title:'Autenticación de dos factores',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
actions:'Acciones',
|
email: {
|
||||||
auth: {
|
notifySendVerification:
|
||||||
forgotPassword: {
|
"Se ha enviado un nuevo enlace de verificación a su dirección de correo electrónico.",
|
||||||
ask: '¿Olvidaste tu contraseña?',
|
sendVerification:
|
||||||
description: '¿Ha olvidado su contraseña? No hay problema. Sólo tienes que indicarnos tu dirección de correo electrónico y te enviaremos un enlace para restablecer la contraseña que te permitirá elegir una nueva.',
|
"Haga clic aquí para volver a enviar el correo electrónico de verificación.",
|
||||||
sendLink: 'Enviar enlace de recuperación por correo',
|
unverify: "Su dirección de correo electrónico no está verificada.",
|
||||||
title: 'Contraseña olvidada',
|
|
||||||
},
|
|
||||||
login: 'Iniciar sesión',
|
|
||||||
logout: 'Cerrar sesión',
|
|
||||||
register: {
|
|
||||||
already: '¿Ya estas registrado?',
|
|
||||||
me: 'Registrarme',
|
|
||||||
},
|
|
||||||
remember: 'Recuerdame',
|
|
||||||
},
|
},
|
||||||
code:'Código',
|
manage: "Administrar cuenta",
|
||||||
cancel:'Cancelar',
|
password: {
|
||||||
changes:'Cambios',
|
description:
|
||||||
changelogs: {
|
"Asegúrese de que su cuenta utiliza una contraseña larga y aleatoria para estar seguro.",
|
||||||
title:'Historial de cambios',
|
new: "Nueva contraseña",
|
||||||
description: 'Lista de los cambios realizados al sistema.',
|
reset: "Restaurar contraseña",
|
||||||
|
secure:
|
||||||
|
"Esta es una zona segura de la aplicación. Confirme su contraseña antes de continuar.",
|
||||||
|
update: "Actualizar contraseña",
|
||||||
|
verify: "Por su seguridad, confirme su contraseña para continuar.",
|
||||||
},
|
},
|
||||||
close:"Cerrar",
|
profile: {
|
||||||
confirm:'Confirmar',
|
description:
|
||||||
copyright:'Todos los derechos reservados.',
|
"Actualice la información del perfil de su cuenta y su dirección de correo electrónico.",
|
||||||
contact:'Contacto',
|
title: "Información del perfil",
|
||||||
crud: {
|
|
||||||
create: 'Nuevo registro',
|
|
||||||
edit: 'Editar registro',
|
|
||||||
destroy: 'Eliminar registro',
|
|
||||||
show: 'Más detalles',
|
|
||||||
},
|
},
|
||||||
date: 'Fecha',
|
sessions: {
|
||||||
delete:{
|
confirm:
|
||||||
confirm: 'Al presionar ELIMINAR el registro se eliminará permanentemente y no podrá recuperarse.',
|
"Por favor, introduzca su contraseña para confirmar que desea salir de sus otras sesiones de navegación en todos sus dispositivos.",
|
||||||
title: 'Eliminar',
|
description:
|
||||||
|
"Gestiona y cierra tus sesiones activas en otros navegadores y dispositivos.",
|
||||||
|
last: "Último activo",
|
||||||
|
logout: "Cerrar otras sesiones del navegador",
|
||||||
|
onLogout:
|
||||||
|
"Si es necesario, puede cerrar la sesión de todos sus otros navegadores en todos sus dispositivos. A continuación se enumeran algunas de sus sesiones recientes; sin embargo, esta lista puede no ser exhaustiva. Si crees que tu cuenta ha sido comprometida, también deberías actualizar tu contraseña.",
|
||||||
|
this: "Dispositivo actual",
|
||||||
|
title: "Sesiones del navegador",
|
||||||
},
|
},
|
||||||
deleted:'Eliminado',
|
twoFactor: {
|
||||||
description:'Descripción',
|
codes: {
|
||||||
details:'Detalles',
|
regenerate: "Regenerar los códigos de recuperación",
|
||||||
disable:'Deshabilitar',
|
show: "Mostrar códigos de recuperación",
|
||||||
disabled:'Deshabilitado',
|
store:
|
||||||
done:'Hecho.',
|
"Guarde estos códigos de recuperación en un gestor de contraseñas seguro. Pueden utilizarse para recuperar el acceso a su cuenta si se pierde su dispositivo de autenticación de dos factores.",
|
||||||
edit:'Editar',
|
|
||||||
email:{
|
|
||||||
title:'Correo',
|
|
||||||
verification:'Verificar correo'
|
|
||||||
},
|
|
||||||
enable:'Habilitar',
|
|
||||||
endDate:'Fecha Fin',
|
|
||||||
event:'Evento',
|
|
||||||
help: {
|
|
||||||
description:'A continuación se lista la iconografía para entender el funcionamiento del sistema.',
|
|
||||||
home: 'Volver a la pagina de inicio.',
|
|
||||||
title:'Ayuda',
|
|
||||||
},
|
|
||||||
history: {
|
|
||||||
title:'Historial de acciones',
|
|
||||||
description:'Historial de acciones realizadas por los usuarios en orden cronológico.'
|
|
||||||
},
|
|
||||||
home:'Inicio',
|
|
||||||
hour:'Hora',
|
|
||||||
icon:'Icono',
|
|
||||||
maternal:'Apellido materno',
|
|
||||||
menu:'Menú',
|
|
||||||
name:'Nombre',
|
|
||||||
noRecords:'Sin registros',
|
|
||||||
notifications: {
|
|
||||||
readed:'Marcar como leído',
|
|
||||||
deleted:'Notificación eliminada',
|
|
||||||
description:'Notificaciones del usuario',
|
|
||||||
notFound:'Notificación no encontrada',
|
|
||||||
title:'Notificaciones',
|
|
||||||
},
|
|
||||||
password:'Contraseña',
|
|
||||||
passwordConfirmation:'Confirmar contraseña',
|
|
||||||
passwordCurrent:'Contraseña actual',
|
|
||||||
passwordReset:'Restaurar contraseña',
|
|
||||||
paternal:'Apellido paterno',
|
|
||||||
phone:'Teléfono',
|
|
||||||
photo: {
|
|
||||||
new: 'Seleccionar una nueva foto',
|
|
||||||
remove:'Remover foto',
|
|
||||||
title:'Foto',
|
|
||||||
},
|
|
||||||
profile:'Perfil',
|
|
||||||
readed:'Leído',
|
|
||||||
register: {
|
|
||||||
agree:'Estoy de acuerdo con los',
|
|
||||||
privacy:'Política de Privacidad',
|
|
||||||
signUp:'Registrarme',
|
|
||||||
terms:'Términos de Servicio',
|
|
||||||
},
|
|
||||||
registers:{
|
|
||||||
title:'Registros',
|
|
||||||
empty:'Sin registros',
|
|
||||||
},
|
|
||||||
remove: 'Remover',
|
|
||||||
return: 'Regresar',
|
|
||||||
role:'Rol',
|
|
||||||
roles:{
|
|
||||||
create: {
|
|
||||||
title: 'Crear rol',
|
|
||||||
description: 'Estos roles serán usados para dar permisos en el sistema.',
|
|
||||||
onSuccess: 'Rol creado exitosamente',
|
|
||||||
onError: 'Error al crear el role',
|
|
||||||
},
|
|
||||||
deleted:'Rol eliminado',
|
|
||||||
title: 'Roles',
|
|
||||||
},
|
|
||||||
save:'Guardar',
|
|
||||||
saved:'¡Guardado!',
|
|
||||||
search:'Buscar',
|
|
||||||
selected: 'Seleccionado',
|
|
||||||
select: 'Seleccionar',
|
|
||||||
setting: 'Configuración',
|
|
||||||
show: {
|
|
||||||
all:'Mostrar todo',
|
|
||||||
title:'Mostrar',
|
|
||||||
},
|
|
||||||
startDate:'Fecha de inicio',
|
|
||||||
status:'Estado',
|
|
||||||
terms: {
|
|
||||||
agree:'Estoy de acuerdo con los',
|
|
||||||
privacy:'Política de privacidad',
|
|
||||||
service:'Términos de servicio',
|
|
||||||
},
|
|
||||||
unknown:'Desconocido',
|
|
||||||
update:'Actualizar',
|
|
||||||
updated:'Actualizado',
|
|
||||||
updateFail:'Error al actualizar',
|
|
||||||
unreaded:'No leído',
|
|
||||||
user:'Usuario',
|
|
||||||
users:{
|
|
||||||
create:{
|
|
||||||
title:'Crear usuario',
|
|
||||||
description:'Permite crear nuevos usuarios. No olvides otorgarle roles para que pueda acceder a las partes del sistema deseados.',
|
|
||||||
onSuccess:'Usuario creado',
|
|
||||||
onError:'Ocurrió un error al crear el usuario'
|
|
||||||
},
|
|
||||||
deleted:'Usuario eliminado',
|
|
||||||
notFount:'Usuario no encontrado',
|
|
||||||
password: {
|
|
||||||
description:'Permite actualizar las contraseñas de los usuarios sobreescribiendola.',
|
|
||||||
title:'Actualizar contraseña',
|
|
||||||
},
|
|
||||||
roles: {
|
|
||||||
description:'Actualiza los roles de los usuarios, permitiendo o denegando los accesos a determinadas áreas.',
|
|
||||||
error:{
|
|
||||||
min:'Seleccionar mínimo un role'
|
|
||||||
},
|
|
||||||
title:'Roles de usuario',
|
|
||||||
},
|
|
||||||
menu:'Menú de usuario',
|
|
||||||
select:'Seleccionar un usuario',
|
|
||||||
settings:'Ajustes del usuario',
|
|
||||||
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',
|
|
||||||
},
|
},
|
||||||
|
description:
|
||||||
|
"Añada seguridad adicional a su cuenta mediante la autenticación de dos factores.",
|
||||||
|
isEnable: "Ha activado la autenticación de dos factores.",
|
||||||
|
isNotEnable: {
|
||||||
|
title: "No ha activado la autenticación de dos factores.",
|
||||||
|
description:
|
||||||
|
"Cuando la autenticación de dos factores está activada, se le pedirá un token seguro y aleatorio durante la autenticación. Puedes recuperar este token desde la aplicación Google Authenticator de tu teléfono.",
|
||||||
|
},
|
||||||
|
key: "Llave de configuración",
|
||||||
|
login: {
|
||||||
|
onAuth:
|
||||||
|
"Por favor, confirme el acceso a su cuenta introduciendo el código de autentificación proporcionado por su aplicación de autentificación.",
|
||||||
|
onRecovery:
|
||||||
|
"Confirme el acceso a su cuenta introduciendo uno de sus códigos de recuperación de emergencia.",
|
||||||
|
},
|
||||||
|
onFinish: "Termina de habilitar la autenticación de dos factores.",
|
||||||
|
qr: {
|
||||||
|
isConfirmed:
|
||||||
|
"La autenticación de dos factores ya está activada. Escanee el siguiente código QR con la aplicación de autenticación de su teléfono o introduzca la clave de configuración.",
|
||||||
|
onConfirmed:
|
||||||
|
"Para terminar de habilitar la autenticación de dos factores, escanea el siguiente código QR utilizando la aplicación de autenticación de tu teléfono o introduce la clave de configuración y proporciona el código OTP generado.",
|
||||||
|
},
|
||||||
|
recovery: {
|
||||||
|
code: "Código de recuperación",
|
||||||
|
useAuth: "Utilizar un código de autentificación",
|
||||||
|
useCode: "Utiliza un código de recuperación",
|
||||||
|
},
|
||||||
|
title: "Autenticación de dos factores",
|
||||||
},
|
},
|
||||||
version:'Versión',
|
},
|
||||||
}
|
actions: "Acciones",
|
||||||
|
auth: {
|
||||||
|
forgotPassword: {
|
||||||
|
ask: "¿Olvidaste tu contraseña?",
|
||||||
|
description:
|
||||||
|
"¿Ha olvidado su contraseña? No hay problema. Sólo tienes que indicarnos tu dirección de correo electrónico y te enviaremos un enlace para restablecer la contraseña que te permitirá elegir una nueva.",
|
||||||
|
sendLink: "Enviar enlace de recuperación por correo",
|
||||||
|
title: "Contraseña olvidada",
|
||||||
|
},
|
||||||
|
login: "Iniciar sesión",
|
||||||
|
logout: "Cerrar sesión",
|
||||||
|
register: {
|
||||||
|
already: "¿Ya estas registrado?",
|
||||||
|
me: "Registrarme",
|
||||||
|
},
|
||||||
|
remember: "Recuerdame",
|
||||||
|
},
|
||||||
|
code: "Código",
|
||||||
|
cancel: "Cancelar",
|
||||||
|
changes: "Cambios",
|
||||||
|
changelogs: {
|
||||||
|
title: "Historial de cambios",
|
||||||
|
description: "Lista de los cambios realizados al sistema.",
|
||||||
|
},
|
||||||
|
close: "Cerrar",
|
||||||
|
confirm: "Confirmar",
|
||||||
|
copyright: "Todos los derechos reservados.",
|
||||||
|
contact: "Contacto",
|
||||||
|
crud: {
|
||||||
|
create: "Nuevo registro",
|
||||||
|
edit: "Editar registro",
|
||||||
|
destroy: "Eliminar registro",
|
||||||
|
show: "Más detalles",
|
||||||
|
},
|
||||||
|
date: "Fecha",
|
||||||
|
delete: {
|
||||||
|
confirm:
|
||||||
|
"Al presionar ELIMINAR el registro se eliminará permanentemente y no podrá recuperarse.",
|
||||||
|
title: "Eliminar",
|
||||||
|
},
|
||||||
|
deleted: "Eliminado",
|
||||||
|
description: "Descripción",
|
||||||
|
details: "Detalles",
|
||||||
|
disable: "Deshabilitar",
|
||||||
|
disabled: "Deshabilitado",
|
||||||
|
done: "Hecho.",
|
||||||
|
edit: "Editar",
|
||||||
|
email: {
|
||||||
|
title: "Correo",
|
||||||
|
verification: "Verificar correo",
|
||||||
|
},
|
||||||
|
enable: "Habilitar",
|
||||||
|
endDate: "Fecha Fin",
|
||||||
|
event: "Evento",
|
||||||
|
help: {
|
||||||
|
description:
|
||||||
|
"A continuación se lista la iconografía para entender el funcionamiento del sistema.",
|
||||||
|
home: "Volver a la pagina de inicio.",
|
||||||
|
title: "Ayuda",
|
||||||
|
},
|
||||||
|
history: {
|
||||||
|
title: "Historial de acciones",
|
||||||
|
description:
|
||||||
|
"Historial de acciones realizadas por los usuarios en orden cronológico.",
|
||||||
|
},
|
||||||
|
home: "Inicio",
|
||||||
|
hour: "Hora",
|
||||||
|
icon: "Icono",
|
||||||
|
maternal: "Apellido materno",
|
||||||
|
menu: "Menú",
|
||||||
|
name: "Nombre",
|
||||||
|
noRecords: "Sin registros",
|
||||||
|
notifications: {
|
||||||
|
readed: "Marcar como leído",
|
||||||
|
deleted: "Notificación eliminada",
|
||||||
|
description: "Notificaciones del usuario",
|
||||||
|
notFound: "Notificación no encontrada",
|
||||||
|
title: "Notificaciones",
|
||||||
|
},
|
||||||
|
password: "Contraseña",
|
||||||
|
passwordConfirmation: "Confirmar contraseña",
|
||||||
|
passwordCurrent: "Contraseña actual",
|
||||||
|
passwordReset: "Restaurar contraseña",
|
||||||
|
paternal: "Apellido paterno",
|
||||||
|
phone: "Teléfono",
|
||||||
|
photo: {
|
||||||
|
new: "Seleccionar una nueva foto",
|
||||||
|
remove: "Remover foto",
|
||||||
|
title: "Foto",
|
||||||
|
},
|
||||||
|
profile: "Perfil",
|
||||||
|
readed: "Leído",
|
||||||
|
register: {
|
||||||
|
agree: "Estoy de acuerdo con los",
|
||||||
|
privacy: "Política de Privacidad",
|
||||||
|
signUp: "Registrarme",
|
||||||
|
terms: "Términos de Servicio",
|
||||||
|
},
|
||||||
|
registers: {
|
||||||
|
title: "Registros",
|
||||||
|
empty: "Sin registros",
|
||||||
|
},
|
||||||
|
remove: "Remover",
|
||||||
|
return: "Regresar",
|
||||||
|
role: "Rol",
|
||||||
|
roles: {
|
||||||
|
create: {
|
||||||
|
title: "Crear rol",
|
||||||
|
description: "Estos roles serán usados para dar permisos en el sistema.",
|
||||||
|
onSuccess: "Rol creado exitosamente",
|
||||||
|
onError: "Error al crear el role",
|
||||||
|
},
|
||||||
|
deleted: "Rol eliminado",
|
||||||
|
title: "Roles",
|
||||||
|
},
|
||||||
|
save: "Guardar",
|
||||||
|
saved: "¡Guardado!",
|
||||||
|
search: "Buscar",
|
||||||
|
selected: "Seleccionado",
|
||||||
|
select: "Seleccionar",
|
||||||
|
setting: "Configuración",
|
||||||
|
show: {
|
||||||
|
all: "Mostrar todo",
|
||||||
|
title: "Mostrar",
|
||||||
|
},
|
||||||
|
startDate: "Fecha de inicio",
|
||||||
|
status: "Estado",
|
||||||
|
terms: {
|
||||||
|
agree: "Estoy de acuerdo con los",
|
||||||
|
privacy: "Política de privacidad",
|
||||||
|
service: "Términos de servicio",
|
||||||
|
},
|
||||||
|
unknown: "Desconocido",
|
||||||
|
update: "Actualizar",
|
||||||
|
updated: "Actualizado",
|
||||||
|
updateFail: "Error al actualizar",
|
||||||
|
unreaded: "No leído",
|
||||||
|
user: "Usuario",
|
||||||
|
users: {
|
||||||
|
create: {
|
||||||
|
title: "Crear usuario",
|
||||||
|
description:
|
||||||
|
"Permite crear nuevos usuarios. No olvides otorgarle roles para que pueda acceder a las partes del sistema deseados.",
|
||||||
|
onSuccess: "Usuario creado",
|
||||||
|
onError: "Ocurrió un error al crear el usuario",
|
||||||
|
},
|
||||||
|
deleted: "Usuario eliminado",
|
||||||
|
notFount: "Usuario no encontrado",
|
||||||
|
password: {
|
||||||
|
description:
|
||||||
|
"Permite actualizar las contraseñas de los usuarios sobreescribiendola.",
|
||||||
|
title: "Actualizar contraseña",
|
||||||
|
},
|
||||||
|
roles: {
|
||||||
|
description:
|
||||||
|
"Actualiza los roles de los usuarios, permitiendo o denegando los accesos a determinadas áreas.",
|
||||||
|
error: {
|
||||||
|
min: "Seleccionar mínimo un role",
|
||||||
|
},
|
||||||
|
title: "Roles de usuario",
|
||||||
|
},
|
||||||
|
menu: "Menú de usuario",
|
||||||
|
select: "Seleccionar un usuario",
|
||||||
|
settings: "Ajustes del usuario",
|
||||||
|
system: "Usuarios del sistema",
|
||||||
|
title: "Usuarios",
|
||||||
|
},
|
||||||
|
|
||||||
|
scores: {
|
||||||
|
system: "Sistema de puntuación",
|
||||||
|
title: "Puntuaciones",
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
select:{
|
||||||
|
title: "Seleccionar un departamento",
|
||||||
|
description: "Seleccione un departamento para filtrar los roles.",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mainRole: {
|
||||||
|
title: "Roles principales",
|
||||||
|
inDepartment: "Roles principales en el departamento",
|
||||||
|
system: "Sistema de roles principales",
|
||||||
|
select:{
|
||||||
|
description: "Seleccione un rol principal.",
|
||||||
|
},
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
skill: {
|
||||||
|
title: "Habilidades",
|
||||||
|
assignTo: "Asignar a rol principal",
|
||||||
|
system: "Sistema de habilidades",
|
||||||
|
assignment: {
|
||||||
|
title: "Habilidades del Rol",
|
||||||
|
description: "Asigna una habilidad a un rol principal.",
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
title: "Crear habilidad",
|
||||||
|
description:
|
||||||
|
"Permite crear nuevas habilidades para los roles principales.",
|
||||||
|
onSuccess: "Habilidad creada exitosamente",
|
||||||
|
onError: "Error al crear la habilidad",
|
||||||
|
},
|
||||||
|
deleted: "Habilidad eliminada",
|
||||||
|
},
|
||||||
|
mainRoleSkills: {
|
||||||
|
system: "Sistema de habilidades de rol principal",
|
||||||
|
create: {
|
||||||
|
title: "Crear habilidad de rol principal",
|
||||||
|
description:
|
||||||
|
"Permite crear nuevas habilidades para los roles principales.",
|
||||||
|
onSuccess: "Habilidad de rol principal creada exitosamente",
|
||||||
|
onError: "Error al crear la habilidad de rol principal",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
version: "Versión",
|
||||||
|
};
|
||||||
|
|||||||
@ -85,11 +85,21 @@ onMounted(()=> {
|
|||||||
name="Rol Principal"
|
name="Rol Principal"
|
||||||
to="admin.mainRoles.index"
|
to="admin.mainRoles.index"
|
||||||
/>
|
/>
|
||||||
|
<Link
|
||||||
|
icon="psychology"
|
||||||
|
name="Habilidades"
|
||||||
|
to="admin.skills.index"
|
||||||
|
/>
|
||||||
<Link
|
<Link
|
||||||
icon="leaderboard"
|
icon="leaderboard"
|
||||||
name="Scores"
|
name="Scores"
|
||||||
to="admin.scores.index"
|
to="admin.scores.index"
|
||||||
/>
|
/>
|
||||||
|
<Link
|
||||||
|
icon="sports_handball"
|
||||||
|
name="Roles Puntuados"
|
||||||
|
to="admin.mainRoleSkills.index"
|
||||||
|
/>
|
||||||
<Link
|
<Link
|
||||||
icon="history"
|
icon="history"
|
||||||
name="changelogs.title"
|
name="changelogs.title"
|
||||||
|
|||||||
@ -65,7 +65,7 @@ const submit = () => form.post(route(goTo('store')), {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
id="Descrición"
|
id="Descripción"
|
||||||
class="col-span-2"
|
class="col-span-2"
|
||||||
v-model="form.description"
|
v-model="form.description"
|
||||||
:onError="form.errors.description"
|
:onError="form.errors.description"
|
||||||
|
|||||||
@ -1,73 +1,64 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { goTo } from './Component';
|
import { goTo } from "./Component";
|
||||||
import { useForm } from '@inertiajs/vue3';
|
import { useForm } from "@inertiajs/vue3";
|
||||||
import { onUpdated } from 'vue';
|
import { onUpdated } from "vue";
|
||||||
|
|
||||||
import Input from '@/Components/Dashboard/Form/Input.vue';
|
import Input from "@/Components/Dashboard/Form/Input.vue";
|
||||||
import EditModal from '@/Components/Dashboard/Modal/Edit.vue';
|
import EditModal from "@/Components/Dashboard/Modal/Edit.vue";
|
||||||
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
|
import Header from "@/Components/Dashboard/Modal/Elements/Header.vue";
|
||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits(["close", "switchModal"]);
|
||||||
'close',
|
|
||||||
'switchModal'
|
|
||||||
]);
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: Boolean,
|
show: Boolean,
|
||||||
model: Object
|
model: Object,
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm({});
|
const form = useForm({});
|
||||||
|
|
||||||
const update = (id) => {
|
const update = (id) => {
|
||||||
form.transform(data => ({
|
form
|
||||||
...props.model
|
.transform((data) => ({
|
||||||
})).put(route(goTo('update'), {id}),{
|
...props.model,
|
||||||
preserveScroll: true,
|
}))
|
||||||
onSuccess: () => {
|
.put(route(goTo("update"), { id }), {
|
||||||
Notify.success(lang('updated'))
|
preserveScroll: true,
|
||||||
emit('switchModal')
|
onSuccess: () => {
|
||||||
}
|
Notify.success(lang("updated"));
|
||||||
|
emit("switchModal");
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<EditModal
|
<EditModal :show="show" @close="$emit('close')" @update="update(model.id)">
|
||||||
:show="show"
|
<Header :title="model.alias" />
|
||||||
@close="$emit('close')"
|
<div class="py-2 border-b">
|
||||||
@update="update(model.id)"
|
<div class="p-4">
|
||||||
>
|
<form>
|
||||||
<Header
|
<div class="grid gap-6 mb-6 lg:grid-cols-2">
|
||||||
:title="model.alias"
|
<Input
|
||||||
/>
|
id="Alias"
|
||||||
<div class="py-2 border-b">
|
placeholder="Alias"
|
||||||
<div class="p-4">
|
v-model="model.alias"
|
||||||
<form>
|
:onError="form.errors.alias"
|
||||||
<div class="grid gap-6 mb-6 lg:grid-cols-2">
|
required
|
||||||
<Input
|
/>
|
||||||
id="Alias"
|
<Input
|
||||||
placeholder="Alias"
|
id="Valor"
|
||||||
v-model="model.alias"
|
v-model="model.value"
|
||||||
:onError="form.errors.alias"
|
:onError="form.errors.value"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
id="Valor"
|
id="Descripción"
|
||||||
v-model="model.value"
|
v-model="model.description"
|
||||||
:onError="form.errors.value"
|
:onError="form.errors.description"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Input
|
</div>
|
||||||
id="Descripción"
|
</form>
|
||||||
v-model="model.description"
|
</div>
|
||||||
:onError="form.errors.description"
|
</div>
|
||||||
required
|
</EditModal>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</EditModal>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -54,7 +54,7 @@ const submit = () => form.post(route(goTo('store')), {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
id="Descrición"
|
id="Descripción"
|
||||||
class="col-span-2"
|
class="col-span-2"
|
||||||
v-model="form.description"
|
v-model="form.description"
|
||||||
:onError="form.errors.description"
|
:onError="form.errors.description"
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import ShowView from './Show.vue';
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
departments: Object
|
departments: Object
|
||||||
});
|
});
|
||||||
|
|
||||||
// Controladores
|
// Controladores
|
||||||
const Modal = new ModalController();
|
const Modal = new ModalController();
|
||||||
const Searcher = new SearcherController(goTo('index'));
|
const Searcher = new SearcherController(goTo('index'));
|
||||||
|
|||||||
@ -59,7 +59,7 @@ const submit = () =>
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
id="Descrición"
|
id="Descripción"
|
||||||
class="col-span-2"
|
class="col-span-2"
|
||||||
v-model="form.description"
|
v-model="form.description"
|
||||||
:onError="form.errors.description"
|
:onError="form.errors.description"
|
||||||
|
|||||||
15
resources/js/Pages/Admin/MainRoleSkills/Component.js
Normal file
15
resources/js/Pages/Admin/MainRoleSkills/Component.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { t } from '@/Lang/i18n';
|
||||||
|
import { hasPermission } from '@/rolePermission.js';
|
||||||
|
|
||||||
|
// Obtener ruta
|
||||||
|
const goTo = (route) => `admin.mainRoleSkills.${route}`
|
||||||
|
// Obtener traducción del componente
|
||||||
|
const transl = (lang) => t(`mainRoleSkills.${lang}`)
|
||||||
|
// Determina si un usuario puede hacer algo no en base a los permisos
|
||||||
|
const can = (permission) => hasPermission(`users.${permission}`)
|
||||||
|
|
||||||
|
export{
|
||||||
|
can,
|
||||||
|
goTo,
|
||||||
|
transl
|
||||||
|
}
|
||||||
169
resources/js/Pages/Admin/MainRoleSkills/Create.vue
Normal file
169
resources/js/Pages/Admin/MainRoleSkills/Create.vue
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<script setup>
|
||||||
|
import { goTo, transl } from "./Component";
|
||||||
|
import { Link, useForm } from "@inertiajs/vue3";
|
||||||
|
import { ref, computed, onMounted } from "vue";
|
||||||
|
|
||||||
|
import PageHeader from "@/Components/Dashboard/PageHeader.vue";
|
||||||
|
import GoogleIcon from "@/Components/Shared/GoogleIcon.vue";
|
||||||
|
import DashboardLayout from "@/Layouts/DashboardLayout.vue";
|
||||||
|
import DepartmentSelector from "@/Components/App/DepartmentSelector.vue";
|
||||||
|
import MainRoleSelector from "@/Components/App/MainRoleSelector.vue";
|
||||||
|
import SkillAssignment from "@/Components/App/SkillAssignment.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
mainRoles: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
skills: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
scores: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
selectedRole: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
selectedDepartment: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedDepartment = ref(props.selectedDepartment);
|
||||||
|
const selectedMainRole = ref(props.selectedRole);
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
main_role_id: props.selectedRole?.id || '',
|
||||||
|
skills: []
|
||||||
|
});
|
||||||
|
|
||||||
|
// Obtener departamentos únicos
|
||||||
|
const departments = computed(() => {
|
||||||
|
const uniqueDepartments = [];
|
||||||
|
const seenIds = new Set();
|
||||||
|
|
||||||
|
props.mainRoles.forEach(role => {
|
||||||
|
if (role.department && !seenIds.has(role.department.id)) {
|
||||||
|
seenIds.add(role.department.id);
|
||||||
|
uniqueDepartments.push(role.department);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return uniqueDepartments;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filtrar habilidades por departamento seleccionado
|
||||||
|
const departmentSkills = computed(() => {
|
||||||
|
if (!selectedDepartment.value) return [];
|
||||||
|
return props.skills.filter(skill =>
|
||||||
|
skill.department_id === selectedDepartment.value.id
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDepartmentSelect = (department) => {
|
||||||
|
selectedDepartment.value = department;
|
||||||
|
selectedMainRole.value = null;
|
||||||
|
form.main_role_id = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRoleSelect = (role) => {
|
||||||
|
selectedMainRole.value = role;
|
||||||
|
form.main_role_id = role.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = (skillsData) => {
|
||||||
|
form.skills = skillsData;
|
||||||
|
|
||||||
|
form.post(route(goTo("store")), {
|
||||||
|
onSuccess: () => {
|
||||||
|
// Redireccionar al index
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
// Mostrar mensaje de error
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
if (selectedMainRole.value) {
|
||||||
|
selectedMainRole.value = null;
|
||||||
|
form.main_role_id = '';
|
||||||
|
} else if (selectedDepartment.value) {
|
||||||
|
selectedDepartment.value = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Si tenemos rol seleccionado, asegurar que el departamento también esté seleccionado
|
||||||
|
if (props.selectedRole && props.selectedRole.department) {
|
||||||
|
selectedDepartment.value = props.selectedRole.department;
|
||||||
|
selectedMainRole.value = props.selectedRole;
|
||||||
|
form.main_role_id = props.selectedRole.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</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">
|
||||||
|
<!-- Navegación hacia atrás -->
|
||||||
|
<div v-if="selectedDepartment || selectedMainRole" class="mb-6">
|
||||||
|
<button
|
||||||
|
@click="goBack"
|
||||||
|
class="text-primary hover:underline flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<GoogleIcon name="arrow_back" class="w-4 h-4" />
|
||||||
|
<span v-if="selectedMainRole">Cambiar rol principal</span>
|
||||||
|
<span v-else-if="selectedDepartment">Cambiar departamento</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Paso 1: Selector de Departamento -->
|
||||||
|
<DepartmentSelector
|
||||||
|
v-if="!selectedDepartment"
|
||||||
|
:departments="departments"
|
||||||
|
@select="handleDepartmentSelect"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Paso 2: Selector de Rol Principal -->
|
||||||
|
<MainRoleSelector
|
||||||
|
v-else-if="selectedDepartment && !selectedMainRole"
|
||||||
|
:selectedDepartment="selectedDepartment"
|
||||||
|
:mainRoles="mainRoles"
|
||||||
|
@select="handleRoleSelect"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Paso 3: Asignación de Habilidades -->
|
||||||
|
<SkillAssignment
|
||||||
|
v-else-if="selectedDepartment && selectedMainRole"
|
||||||
|
:form="form"
|
||||||
|
:skills="departmentSkills"
|
||||||
|
:scores="scores"
|
||||||
|
:selectedDepartment="selectedDepartment"
|
||||||
|
:selectedMainRole="selectedMainRole"
|
||||||
|
@submit="submit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DashboardLayout>
|
||||||
|
</template>
|
||||||
42
resources/js/Pages/Admin/MainRoleSkills/Destroy.vue
Normal file
42
resources/js/Pages/Admin/MainRoleSkills/Destroy.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<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.skill.name"
|
||||||
|
/>
|
||||||
|
</DestroyModal>
|
||||||
|
</template>
|
||||||
87
resources/js/Pages/Admin/MainRoleSkills/Edit.vue
Normal file
87
resources/js/Pages/Admin/MainRoleSkills/Edit.vue
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<script setup>
|
||||||
|
import { goTo } from "./Component";
|
||||||
|
import { router, useForm } from "@inertiajs/vue3";
|
||||||
|
|
||||||
|
import Selectable from "@/Components/Dashboard/Form/Selectable.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,
|
||||||
|
scores: Array,
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
scored_id: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const update = (id) => {
|
||||||
|
form
|
||||||
|
.transform((data) => ({
|
||||||
|
scored_id:
|
||||||
|
typeof data.scored_id === "object" ? data.scored_id.id : data.scored_id,
|
||||||
|
}))
|
||||||
|
.put(route(goTo("update"), { id }), {
|
||||||
|
preserveScroll: true,
|
||||||
|
onSuccess: () => {
|
||||||
|
Notify.success(lang("updated"));
|
||||||
|
emit("switchModal");
|
||||||
|
router.reload();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<EditModal :show="show" @close="$emit('close')" @update="update(model.id)">
|
||||||
|
<Header :title="model.main_role?.name" />
|
||||||
|
<div class="py-2 border-b">
|
||||||
|
<div class="p-4">
|
||||||
|
<form>
|
||||||
|
<div class="grid gap-6 mb-6 overflow-auto">
|
||||||
|
<div class="bg-gray-50 p-4 rounded-lg">
|
||||||
|
<h4 class="bg-gray-50 text-gray-700 mb-2 font-medium">
|
||||||
|
Información:
|
||||||
|
</h4>
|
||||||
|
<div class="text-sm text-gray-600 space-y-1">
|
||||||
|
<p>
|
||||||
|
<span class="font-medium">Rol: </span
|
||||||
|
>{{ model.main_role?.name }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="font-medium">Departamento: </span>
|
||||||
|
{{ model.main_role?.department?.name }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="font-medium">Habilidad: </span
|
||||||
|
>{{ model.skill?.name }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Cambiar Puntuación
|
||||||
|
</label>
|
||||||
|
<Selectable
|
||||||
|
id="scored_id"
|
||||||
|
v-model="form.scored_id"
|
||||||
|
:options="scores"
|
||||||
|
:onError="form.errors.scored_id"
|
||||||
|
label="alias"
|
||||||
|
track-by="id"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p class="text-xs text-gray-500 mt-1">
|
||||||
|
Puntuación actual:
|
||||||
|
<span class="font-medium"> {{ model.score?.alias }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</EditModal>
|
||||||
|
</template>
|
||||||
92
resources/js/Pages/Admin/MainRoleSkills/Index.vue
Normal file
92
resources/js/Pages/Admin/MainRoleSkills/Index.vue
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<script setup>
|
||||||
|
import { transl } from "./Component";
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
|
||||||
|
import DashboardLayout from "@/Layouts/DashboardLayout.vue";
|
||||||
|
import DepartmentSelector from "@/Components/App/DepartmentSelector.vue";
|
||||||
|
import MainRoleSelector from "@/Components/App/MainRoleSelector.vue";
|
||||||
|
import SkillRole from "@/Components/App/SkillsRole.vue";
|
||||||
|
import PageHeader from "@/Components/Dashboard/PageHeader.vue";
|
||||||
|
import GoogleIcon from "@/Components/Shared/GoogleIcon.vue";
|
||||||
|
|
||||||
|
defineEmits(["select"]);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
mainRoleSkills: Object,
|
||||||
|
departments: Object,
|
||||||
|
mainRoles: Object,
|
||||||
|
skills: Object,
|
||||||
|
scores: Object,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedDepartment = ref(null);
|
||||||
|
const selectedRole = ref(null);
|
||||||
|
|
||||||
|
const filteredMainRoleSkills = computed(() => {
|
||||||
|
if (!props.mainRoleSkills?.data || !selectedRole.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.mainRoleSkills.data.filter(roleSkill =>
|
||||||
|
roleSkill.main_role_id === selectedRole.value.id
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDepartmentSelect = (department) => {
|
||||||
|
selectedDepartment.value = department;
|
||||||
|
selectedRole.value= null;
|
||||||
|
};
|
||||||
|
const handleRoleSelect = (role) => {
|
||||||
|
selectedRole.value = role;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSkillSelect = (roleSkill) => {
|
||||||
|
console.log('Habilidad de rol seleccionada:', roleSkill);
|
||||||
|
}
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
if (selectedRole.value) {
|
||||||
|
selectedRole.value = null;
|
||||||
|
} else if (selectedDepartment.value) {
|
||||||
|
selectedDepartment.value = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DashboardLayout :title="transl('system')">
|
||||||
|
<PageHeader>
|
||||||
|
<button @click="goBack">
|
||||||
|
<GoogleIcon
|
||||||
|
:title="$t('return')"
|
||||||
|
class="btn-icon-primary"
|
||||||
|
name="arrow_back"
|
||||||
|
outline
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</PageHeader>
|
||||||
|
|
||||||
|
<!-- Selector de Departamento -->
|
||||||
|
<DepartmentSelector
|
||||||
|
v-if="!selectedDepartment"
|
||||||
|
:departments="departments"
|
||||||
|
@select="handleDepartmentSelect"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Selector de Roles Principales -->
|
||||||
|
<MainRoleSelector
|
||||||
|
v-if="selectedDepartment"
|
||||||
|
:selectedDepartment="selectedDepartment"
|
||||||
|
:mainRoles="mainRoles"
|
||||||
|
@select="handleRoleSelect"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SkillRole
|
||||||
|
v-if="selectedRole"
|
||||||
|
:mainRoleSkills="filteredMainRoleSkills"
|
||||||
|
:selectedRole="selectedRole"
|
||||||
|
:scores="scores"
|
||||||
|
@select="handleSkillSelect"
|
||||||
|
/>
|
||||||
|
</DashboardLayout>
|
||||||
|
</template>
|
||||||
15
resources/js/Pages/Admin/Skills/Component.js
Normal file
15
resources/js/Pages/Admin/Skills/Component.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { t } from '@/Lang/i18n';
|
||||||
|
import { hasPermission } from '@/rolePermission.js';
|
||||||
|
|
||||||
|
// Obtener ruta
|
||||||
|
const goTo = (route) => `admin.skills.${route}`
|
||||||
|
// Obtener traducción del componente
|
||||||
|
const transl = (lang) => t(`skill.${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/Skills/Create.vue
Normal file
89
resources/js/Pages/Admin/Skills/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="Descripció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/Skills/Destroy.vue
Normal file
43
resources/js/Pages/Admin/Skills/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/Skills/Edit.vue
Normal file
67
resources/js/Pages/Admin/Skills/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>
|
||||||
149
resources/js/Pages/Admin/Skills/Index.vue
Normal file
149
resources/js/Pages/Admin/Skills/Index.vue
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<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';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
skills: 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 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="skills"
|
||||||
|
@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>
|
||||||
@ -7,8 +7,11 @@
|
|||||||
use App\Http\Controllers\Dashboard\NotificationController;
|
use App\Http\Controllers\Dashboard\NotificationController;
|
||||||
use App\Http\Controllers\Admin\DepartmentController;
|
use App\Http\Controllers\Admin\DepartmentController;
|
||||||
use App\Http\Controllers\Admin\MainRoleController;
|
use App\Http\Controllers\Admin\MainRoleController;
|
||||||
|
use App\Http\Controllers\Admin\MainRoleSkillsController;
|
||||||
|
use App\Http\Controllers\Admin\SkillController;
|
||||||
use App\Http\Controllers\Developer\RoleController;
|
use App\Http\Controllers\Developer\RoleController;
|
||||||
use App\Http\Controllers\Example\IndexController as ExampleIndexController;
|
use App\Http\Controllers\Example\IndexController as ExampleIndexController;
|
||||||
|
use App\Models\MainRoleSkills;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,7 +20,6 @@
|
|||||||
* Rutas accesibles por todos los usuarios y no usuarios
|
* Rutas accesibles por todos los usuarios y no usuarios
|
||||||
*/
|
*/
|
||||||
Route::redirect('/', '/login');
|
Route::redirect('/', '/login');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rutas del Dashboard
|
* Rutas del Dashboard
|
||||||
*
|
*
|
||||||
@ -55,10 +57,12 @@
|
|||||||
'auth:sanctum',
|
'auth:sanctum',
|
||||||
config('jetstream.auth_session')
|
config('jetstream.auth_session')
|
||||||
])->group(function () {
|
])->group(function () {
|
||||||
Route::resource('users', UserController::class);
|
Route::resource('users', UserController::class);
|
||||||
Route::resource('scores', ScoreController::class);
|
Route::resource('scores', ScoreController::class);
|
||||||
Route::resource('departments', DepartmentController::class);
|
Route::resource('departments', DepartmentController::class);
|
||||||
Route::resource('mainRoles', MainRoleController::class);
|
Route::resource('mainRoles', MainRoleController::class);
|
||||||
|
Route::resource('skills', SkillController::class);
|
||||||
|
Route::resource('mainRoleSkills', MainRoleSkillsController::class);
|
||||||
|
|
||||||
Route::prefix('/users')->name('users.')->group(function()
|
Route::prefix('/users')->name('users.')->group(function()
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user