Se crearon los crud de Puntajes, Departamentos y Role principal

This commit is contained in:
Juan Felipe Zapata Moreno 2025-07-01 16:32:56 -06:00
parent be89d18e74
commit 5af127d5f9
31 changed files with 1475 additions and 16 deletions

View File

@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers\Admin;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use App\Http\Requests\StoreDepartment;
use App\Http\Requests\UpdateDepartment;
use App\Models\department;
use Notsoweb\Core\Http\Controllers\VueController;
/**
* Descripción
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class DepartmentController extends VueController
{
public function __construct()
{
$this->vueRoot('admin.departments');
}
public function index()
{
$q = request()->get('q');
return $this->vuew('index', [
'departments' => department::where('name', 'LIKE', "%{$q}%")
->orWhere('description', 'LIKE', "%{$q}%")
->select([
'id',
'name',
'description',
])
->paginate(config('app.pagination'))
]);
}
public function create()
{
return $this->vuew('create');
}
public function store(StoreDepartment $request)
{
$data = $request->all();
department::create($data);
return $this->index();
}
public function update(UpdateDepartment $request, $department)
{
$data = $request->all();
department::find($department)->update($data);
}
public function destroy($department)
{
department::find($department)->delete();
return $this->index();
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Admin;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use App\Models\MainRole;
use Illuminate\Http\Request;
use Notsoweb\Core\Http\Controllers\VueController;
/**
* Descripción
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class MainRoleController extends VueController
{
public function __construct()
{
$this->vueRoot('admin.main-role');
}
public function index()
{
$q = request()->get('q');
return $this->vuew('index', [
'roles' => MainRole::where('name', 'LIKE', "%{$q}%")
->orWhere('description', 'LIKE', "%{$q}%")
->select([
'id',
'name',
'description',
])
->paginate(config('app.pagination'))
]);
}
}

View File

@ -0,0 +1,71 @@
<?php namespace App\Http\Controllers\Admin;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use App\Models\Score;
use App\Http\Requests\StoreScore;
use App\Http\Requests\UpdateScore;
use Notsoweb\Core\Http\Controllers\VueController;
/**
* Descripción
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class ScoreController extends VueController
{
public function __construct()
{
$this->vueRoot('admin.app');
}
public function index()
{
$q = request()->get('q');
return $this->vuew('index', [
'scores' => Score::where('alias', 'LIKE', "%{$q}%")
->orWhere('value', 'LIKE', "%{$q}%")
->select([
'id',
'alias',
'value',
'description',
])
->paginate(config('app.pagination'))
]);
}
public function create()
{
return $this->vuew('create');
}
public function store(StoreScore $request)
{
$data = $request->all();
Score::create($data);
return $this->index();
}
public function update(UpdateScore $request, $score)
{
$data = $request->all();
Score::find($score)->update($data);
}
public function destroy($score)
{
Score::find($score)->delete();
return $this->index();
}
}

View File

@ -0,0 +1,39 @@
<?php namespace App\Http\Requests;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use Illuminate\Foundation\Http\FormRequest;
/**
* Valida el almacenamiento de un usuario
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class StoreDepartment extends FormRequest
{
/**
* Determinar si el usuario esta autorizado
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Reglas de validación
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'name' => ['required', 'string'],
'description' => ['nullable', 'string'],
];
}
}

View File

@ -0,0 +1,39 @@
<?php namespace App\Http\Requests;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use Illuminate\Foundation\Http\FormRequest;
/**
* Valida el almacenamiento de un usuario
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class StoreMainRole extends FormRequest
{
/**
* Determinar si el usuario esta autorizado
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Reglas de validación
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'name' => ['required', 'string'],
'description' => ['nullable', 'string'],
];
}
}

View File

@ -0,0 +1,40 @@
<?php namespace App\Http\Requests;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use Illuminate\Foundation\Http\FormRequest;
/**
* Valida el almacenamiento de un usuario
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class StoreScore extends FormRequest
{
/**
* Determinar si el usuario esta autorizado
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Reglas de validación
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'alias' => ['required', 'string'],
'value' => ['required', 'string'],
'description' => ['nullable', 'string'],
];
}
}

View File

@ -0,0 +1,40 @@
<?php namespace App\Http\Requests;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
/**
* Valida la actualización de los usuarios
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class UpdateDepartment extends FormRequest
{
/**
* Determinar si el usuario esta autorizado
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Reglas de validación
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'name' => ['required', 'string'],
'description' => ['nullable', 'string'],
];
}
}

View File

@ -0,0 +1,39 @@
<?php namespace App\Http\Requests;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use Illuminate\Foundation\Http\FormRequest;
/**
* Valida el almacenamiento de un usuario
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class UpdateMainRole extends FormRequest
{
/**
* Determinar si el usuario esta autorizado
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Reglas de validación
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'name' => ['required', 'string'],
'description' => ['nullable', 'string'],
];
}
}

View File

@ -0,0 +1,41 @@
<?php namespace App\Http\Requests;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
/**
* Valida la actualización de los usuarios
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class UpdateScore extends FormRequest
{
/**
* Determinar si el usuario esta autorizado
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Reglas de validación
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'alias' => ['required', 'string'],
'value' => ['required', 'string'],
'description' => ['nullable', 'string'],
];
}
}

30
app/Models/Score.php Normal file
View File

@ -0,0 +1,30 @@
<?php namespace App\Models;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use App\Http\Traits\ModelExtend;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* Descripción
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class Score extends Model
{
use HasFactory,
ModelExtend;
/**
* Atributos llenables masivamente
*/
protected $fillable = [
'alias',
'value',
'description',
];
}

37
app/Models/department.php Normal file
View File

@ -0,0 +1,37 @@
<?php namespace App\Models;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use App\Http\Traits\ModelExtend;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* Descripción
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class department extends Model
{
use HasFactory,
ModelExtend;
/**
* Atributos llenables masivamente
*/
protected $fillable = [
'name',
'description'
];
/**
* Un departamento tiene muchos main roles
*/
public function mainRoles()
{
return $this->hasMany(MainRole::class);
}
}

43
app/Models/main_role.php Normal file
View File

@ -0,0 +1,43 @@
<?php namespace App\Models;
/**
* @copyright Copyright (c) 2023 Notsoweb (https://notsoweb.com) - All rights reserved.
*/
use App\Http\Traits\ModelExtend;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* Roles principales del sistema
*
* @author Moisés de Jesús Cortés Castellanos <ing.moisesdejesuscortesc@notsoweb.com>
*
* @version 1.0.0
*/
class MainRole extends Model
{
use HasFactory,
ModelExtend;
/**
* Nombre de la tabla
*/
protected $table = 'main_role';
/**
* Atributos llenables masivamente
*/
protected $fillable = [
'name',
'description',
'department_id'
];
/**
* Un main role pertenece a un departamento
*/
public function department()
{
return $this->belongsTo(Department::class);
}
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('scores', function (Blueprint $table){
$table->id();
$table->string('alias');
$table->integer('value');
$table->string('description')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('scores');
}
};

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('departments', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('description')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('departments');
}
};

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('main_role', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('description')->nullable();
$table->foreignId('department_id')->constrained('departments')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('main_role');
}
};

2
package-lock.json generated
View File

@ -1,5 +1,5 @@
{ {
"name": "template-laravel-vuejs", "name": "Scores",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {

View File

@ -224,5 +224,24 @@ export default {
system:'Usuarios del sistema', system:'Usuarios del sistema',
title:'Usuarios', 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',
create:{
title:'Crear departamento',
description:'Permite crear nuevos departamentos para los usuarios.',
onSuccess:'Departamento creado exitosamente',
onError:'Error al crear el departamento',
}
},
version:'Versión', version:'Versión',
} }

View File

@ -35,7 +35,7 @@ onMounted(()=> {
:title="title" :title="title"
/> />
<div class="flex w-full h-screen"> <div class="flex w-full h-screen">
<div <div
id="sidebar" id="sidebar"
class="fixed w-fit h-screen transition-all duration-300 z-10" class="fixed w-fit h-screen transition-all duration-300 z-10"
:class="{'-translate-x-[16.5rem] md:-translate-x-0':sidebarStatus, '-translate-x-0 md:-translate-x-64':!sidebarStatus}" :class="{'-translate-x-[16.5rem] md:-translate-x-0':sidebarStatus, '-translate-x-0 md:-translate-x-64':!sidebarStatus}"
@ -45,9 +45,9 @@ onMounted(()=> {
@open="sidebarSwitch()" @open="sidebarSwitch()"
> >
<Section name="Principal"> <Section name="Principal">
<Link <Link
icon="home" icon="home"
name="home" name="home"
to="dashboard.index" to="dashboard.index"
/> />
<Link <Link
@ -57,7 +57,7 @@ onMounted(()=> {
/> />
</Section> </Section>
<Section name="Configuraciones"> <Section name="Configuraciones">
<Link <Link
icon="manage_accounts" icon="manage_accounts"
name="profile" name="profile"
to="profile.show" to="profile.show"
@ -69,12 +69,22 @@ onMounted(()=> {
to="dashboard.histories.index" to="dashboard.histories.index"
/> />
</Section> </Section>
<Link <Link
v-if="hasPermission('users.index')" v-if="hasPermission('users.index')"
icon="people" icon="people"
name="users.title" name="users.title"
to="admin.users.index" to="admin.users.index"
/> />
<Link
icon="business_center"
name="Departamentos"
to="admin.departments.index"
/>
<Link
icon="leaderboard"
name="Scores"
to="admin.scores.index"
/>
<Link <Link
icon="history" icon="history"
name="changelogs.title" name="changelogs.title"
@ -93,7 +103,7 @@ onMounted(()=> {
</Section> </Section>
</Sidebar> </Sidebar>
</div> </div>
<div <div
class="flex flex-col w-full transition-all duration-300" class="flex flex-col w-full transition-all duration-300"
:class="{'md:w-[calc(100vw-rem)] md:ml-64':sidebarStatus, 'md:w-screen md:ml-0':!sidebarStatus}" :class="{'md:w-[calc(100vw-rem)] md:ml-64':sidebarStatus, 'md:w-screen md:ml-0':!sidebarStatus}"
> >

View File

@ -0,0 +1,15 @@
import { t } from '@/Lang/i18n';
import { hasPermission } from '@/rolePermission.js';
// Obtener ruta
const goTo = (route) => `admin.scores.${route}`
// Obtener traducción del componente
const transl = (lang) => t(`scores.${lang}`)
// Determina si un usuario puede hacer algo no en base a los permisos
const can = (permission) => hasPermission(`users.${permission}`)
export {
can,
goTo,
transl
}

View File

@ -0,0 +1,84 @@
<script setup>
import { goTo, transl } from './Component';
import { Link, useForm } from '@inertiajs/vue3';
import PrimaryButton from '@/Components/Dashboard/Button/Primary.vue';
import Input from '@/Components/Dashboard/Form/Input.vue';
import PageHeader from '@/Components/Dashboard/PageHeader.vue';
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
import DashboardLayout from '@/Layouts/DashboardLayout.vue';
defineProps({
alias: String,
value: Number,
description: String
});
const form = useForm({
alias: '',
value: '',
description: '',
});
const submit = () => form.post(route(goTo('store')), {
onSuccess: () => Notify.success(transl('create.onSuccess')),
onError: () => Notify.error(transl('create.onError')),
onFinish: () => form.reset('password')
});
</script>
<template>
<DashboardLayout :title="transl('create.title')">
<PageHeader>
<Link :href="route(goTo('index'))">
<GoogleIcon
:title="$t('return')"
class="btn-icon-primary"
name="arrow_back"
outline
/>
</Link>
</PageHeader>
<div class="w-full pb-8">
<div class="mt-8">
<p
v-text="transl('create.description')"
/>
</div>
</div>
<div class="w-full">
<form @submit.prevent="submit" class="grid gap-4 grid-cols-6">
<Input
id="Alias"
class="col-span-2"
v-model="form.alias"
:onError="form.errors.alias"
autofocus
required
/>
<Input
id="Valor"
class="col-span-2"
v-model="form.value"
:onError="form.errors.value"
autofocus
required
/>
<Input
id="Descrición"
class="col-span-2"
v-model="form.description"
:onError="form.errors.description"
autofocus
/>
<div class="col-span-6 flex flex-col items-center justify-end space-y-4 mt-4">
<PrimaryButton
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
v-text="transl('create.title')"
/>
</div>
</form>
</div>
</DashboardLayout>
</template>

View File

@ -0,0 +1,43 @@
<script setup>
import { transl, goTo } from './Component'
import { router } from '@inertiajs/vue3';
import DestroyModal from '@/Components/Dashboard/Modal/Destroy.vue';
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
const emit = defineEmits([
'close',
'switchModal'
]);
const props = defineProps({
show: Boolean,
model: Object
});
const destroy = (id) => router.delete(route(goTo('destroy'), {id}), {
preserveScroll: true,
onSuccess: () => {
props.model.pop;
Notify.success(transl('deleted'));
emit('close');
},
onError: () => {
Notify.info(transl('notFound'));
emit('close');
}
});
</script>
<template>
<DestroyModal
:show="show"
@close="$emit('close')"
@destroy="destroy(model.id)"
>
<Header
:title="model.alias"
:subtitle="model.value"
/>
</DestroyModal>
</template>

View File

@ -0,0 +1,73 @@
<script setup>
import { goTo } from './Component';
import { useForm } from '@inertiajs/vue3';
import { onUpdated } from 'vue';
import Input from '@/Components/Dashboard/Form/Input.vue';
import EditModal from '@/Components/Dashboard/Modal/Edit.vue';
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
const emit = defineEmits([
'close',
'switchModal'
]);
const props = defineProps({
show: Boolean,
model: Object
});
const form = useForm({});
const update = (id) => {
form.transform(data => ({
...props.model
})).put(route(goTo('update'), {id}),{
preserveScroll: true,
onSuccess: () => {
Notify.success(lang('updated'))
emit('switchModal')
}
});
}
</script>
<template>
<EditModal
:show="show"
@close="$emit('close')"
@update="update(model.id)"
>
<Header
:title="model.alias"
/>
<div class="py-2 border-b">
<div class="p-4">
<form>
<div class="grid gap-6 mb-6 lg:grid-cols-2">
<Input
id="Alias"
placeholder="Alias"
v-model="model.alias"
:onError="form.errors.alias"
required
/>
<Input
id="Valor"
v-model="model.value"
:onError="form.errors.value"
required
/>
<Input
id="Descripción"
v-model="model.description"
:onError="form.errors.description"
required
/>
</div>
</form>
</div>
</div>
</EditModal>
</template>

View File

@ -0,0 +1,158 @@
<script setup>
import { transl, can, goTo } from './Component'
import { ref } from 'vue';
import { Link } from '@inertiajs/vue3';
import ModalController from '@/Controllers/ModalController.js';
import SearcherController from '@/Controllers/SearcherController.js';
import SearcherHead from '@/Components/Dashboard/Searcher.vue';
import Table from '@/Components/Dashboard/Table.vue';
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
import DashboardLayout from '@/Layouts/DashboardLayout.vue';
import DestroyView from './Destroy.vue';
import EditView from './Edit.vue';
import ShowView from './Show.vue';
const props = defineProps({
scores: Object
});
// Controladores
const Modal = new ModalController();
const Searcher = new SearcherController(goTo('index'));
// Variables de controladores
const destroyModal = ref(Modal.destroyModal);
const editModal = ref(Modal.editModal);
const showModal = ref(Modal.showModal);
const modelModal = ref(Modal.modelModal);
const query = ref(Searcher.query);
</script>
<template>
<DashboardLayout :title="transl('system')">
<SearcherHead @search="Searcher.search">
<Link
v-if="can('create')"
:href="route(goTo('create'))"
>
<GoogleIcon
:title="$t('crud.create')"
class="btn-icon-primary"
name="add"
outline
/>
</Link>
</SearcherHead>
<div class="pt-2 w-full">
<Table
:items="scores"
@send-pagination="Searcher.searchWithPagination"
>
<template #head>
<th
class="table-item"
v-text="$t('Alias')"
/>
<th
class="table-item"
v-text="$t('Valor')"
/>
<th
class="table-item"
v-text="$t('Descripción')"
/>
<th
class="table-item w-44"
v-text="$t('actions')"
/>
</template>
<template #body="{items}">
<tr v-for="model in items">
<td class="table-item border">
<div class="flex items-center text-sm">
<div>
<p class="font-semibold">
{{ model.alias }}
</p>
</div>
</div>
</td>
<td class="table-item border">
<div class="flex items-center text-sm">
<div>
<p class="font-semibold">
{{ model.value }}
</p>
</div>
</div>
</td>
<td class="table-item border">
<div class="flex items-center text-sm">
<div>
<p class="font-semibold">
{{ model.description }}
</p>
</div>
</div>
</td>
<td class="table-item border">
<div class="flex justify-center space-x-2">
<GoogleIcon
:title="$t('crud.show')"
class="btn-icon-primary"
name="visibility"
outline
@click="Modal.switchShowModal(model)"
/>
<GoogleIcon
v-if="can('edit')"
:title="$t('crud.edit')"
class="btn-icon-primary"
name="edit"
outline
@click="Modal.switchEditModal(model)"
/>
<GoogleIcon
v-if="can('destroy')"
:title="$t('crud.destroy')"
class="btn-icon-primary"
name="delete"
outline
@click="Modal.switchDestroyModal(model)"
/>
</div>
</td>
</tr>
</template>
<template #empty>
<td class="table-item border">
<div class="flex items-center text-sm">
<div>
<p class="font-semibold">
{{ $t('registers.empty') }}
</p>
</div>
</div>
</td>
<td class="table-item border">-</td>
<td class="table-item border">-</td>
</template>
</Table>
</div>
<EditView
v-if="can('edit')"
:show="editModal"
:model="modelModal"
@switchModal="Modal.switchShowEditModal"
@close="Modal.switchEditModal"
/>
<DestroyView
v-if="can('create')"
:show="destroyModal"
:model="modelModal"
@close="Modal.switchDestroyModal"
/>
</DashboardLayout>
</template>

View File

@ -0,0 +1,41 @@
<script setup>
import { can } from './Component'
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
import ShowModal from '@/Components/Dashboard/Modal/Show.vue';
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
defineEmits([
'close',
'switchModal'
]);
defineProps({
show: Boolean,
model: Object
});
</script>
<template>
<ShowModal
:show="show"
:editable="can('edit')"
@close="$emit('close')"
@edit="$emit('switchModal')"
editable
>
<Header
:title="model.alias"
>
</Header>
<div class="py-2 border-b">
<div class="px-4 py-2 flex">
<GoogleIcon
class="text-xl text-success"
name="contact_mail"
/>
<div class="pl-3">
</div>
</div>
</div>
</ShowModal>
</template>

View File

@ -0,0 +1,15 @@
import { t } from '@/Lang/i18n';
import { hasPermission } from '@/rolePermission.js';
// Obtener ruta
const goTo = (route) => `admin.departments.${route}`
// Obtener traducción del componente
const transl = (lang) => t(`department.${lang}`)
// Determina si un usuario puede hacer algo no en base a los permisos
const can = (permission) => hasPermission(`users.${permission}`)
export {
can,
goTo,
transl
}

View File

@ -0,0 +1,74 @@
<script setup>
import { goTo, transl } from './Component';
import { Link, useForm } from '@inertiajs/vue3';
import PrimaryButton from '@/Components/Dashboard/Button/Primary.vue';
import Input from '@/Components/Dashboard/Form/Input.vue';
import PageHeader from '@/Components/Dashboard/PageHeader.vue';
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
import DashboardLayout from '@/Layouts/DashboardLayout.vue';
defineProps({
name: String,
description: String
});
const form = useForm({
name: '',
description: '',
});
const submit = () => form.post(route(goTo('store')), {
onSuccess: () => Notify.success(transl('create.onSuccess')),
onError: () => Notify.error(transl('create.onError')),
onFinish: () => form.reset('password')
});
</script>
<template>
<DashboardLayout :title="transl('create.title')">
<PageHeader>
<Link :href="route(goTo('index'))">
<GoogleIcon
:title="$t('return')"
class="btn-icon-primary"
name="arrow_back"
outline
/>
</Link>
</PageHeader>
<div class="w-full pb-8">
<div class="mt-8">
<p
v-text="transl('create.description')"
/>
</div>
</div>
<div class="w-full">
<form @submit.prevent="submit" class="grid gap-4 grid-cols-6">
<Input
id="Nombre"
class="col-span-2"
v-model="form.name"
:onError="form.errors.name"
autofocus
required
/>
<Input
id="Descrición"
class="col-span-2"
v-model="form.description"
:onError="form.errors.description"
autofocus
/>
<div class="col-span-6 flex flex-col items-center justify-end space-y-4 mt-4">
<PrimaryButton
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
v-text="transl('create.title')"
/>
</div>
</form>
</div>
</DashboardLayout>
</template>

View 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>

View 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>

View File

@ -0,0 +1,145 @@
<script setup>
import { transl, can, goTo } from './Component'
import { ref } from 'vue';
import { Link } from '@inertiajs/vue3';
import ModalController from '@/Controllers/ModalController.js';
import SearcherController from '@/Controllers/SearcherController.js';
import SearcherHead from '@/Components/Dashboard/Searcher.vue';
import Table from '@/Components/Dashboard/Table.vue';
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
import DashboardLayout from '@/Layouts/DashboardLayout.vue';
import DestroyView from './Destroy.vue';
import EditView from './Edit.vue';
import ShowView from './Show.vue';
const props = defineProps({
departments: Object
});
// Controladores
const Modal = new ModalController();
const Searcher = new SearcherController(goTo('index'));
// Variables de controladores
const destroyModal = ref(Modal.destroyModal);
const editModal = ref(Modal.editModal);
const showModal = ref(Modal.showModal);
const modelModal = ref(Modal.modelModal);
const query = ref(Searcher.query);
</script>
<template>
<DashboardLayout :title="transl('system')">
<SearcherHead @search="Searcher.search">
<Link
v-if="can('create')"
:href="route(goTo('create'))"
>
<GoogleIcon
:title="$t('crud.create')"
class="btn-icon-primary"
name="add"
outline
/>
</Link>
</SearcherHead>
<div class="pt-2 w-full">
<Table
:items="departments"
@send-pagination="Searcher.searchWithPagination"
>
<template #head>
<th
class="table-item"
v-text="$t('Nombre')"
/>
<th
class="table-item"
v-text="$t('Descripción')"
/>
<th
class="table-item w-44"
v-text="$t('actions')"
/>
</template>
<template #body="{items}">
<tr v-for="model in items">
<td class="table-item border">
<div class="flex items-center text-sm">
<div>
<p class="font-semibold">
{{ model.name }}
</p>
</div>
</div>
</td>
<td class="table-item border">
<div class="flex items-center text-sm">
<div>
<p class="font-semibold">
{{ model.description }}
</p>
</div>
</div>
</td>
<td class="table-item border">
<div class="flex justify-center space-x-2">
<GoogleIcon
:title="$t('crud.show')"
class="btn-icon-primary"
name="visibility"
outline
@click="Modal.switchShowModal(model)"
/>
<GoogleIcon
v-if="can('edit')"
:title="$t('crud.edit')"
class="btn-icon-primary"
name="edit"
outline
@click="Modal.switchEditModal(model)"
/>
<GoogleIcon
v-if="can('destroy')"
:title="$t('crud.destroy')"
class="btn-icon-primary"
name="delete"
outline
@click="Modal.switchDestroyModal(model)"
/>
</div>
</td>
</tr>
</template>
<template #empty>
<td class="table-item border">
<div class="flex items-center text-sm">
<div>
<p class="font-semibold">
{{ $t('registers.empty') }}
</p>
</div>
</div>
</td>
<td class="table-item border">-</td>
<td class="table-item border">-</td>
</template>
</Table>
</div>
<EditView
v-if="can('edit')"
:show="editModal"
:model="modelModal"
@switchModal="Modal.switchShowEditModal"
@close="Modal.switchEditModal"
/>
<DestroyView
v-if="can('create')"
:show="destroyModal"
:model="modelModal"
@close="Modal.switchDestroyModal"
/>
</DashboardLayout>
</template>

View File

@ -0,0 +1,41 @@
<script setup>
import { can } from './Component'
import Header from '@/Components/Dashboard/Modal/Elements/Header.vue';
import ShowModal from '@/Components/Dashboard/Modal/Show.vue';
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
defineEmits([
'close',
'switchModal'
]);
defineProps({
show: Boolean,
model: Object
});
</script>
<template>
<ShowModal
:show="show"
:editable="can('edit')"
@close="$emit('close')"
@edit="$emit('switchModal')"
editable
>
<Header
:title="model.alias"
>
</Header>
<div class="py-2 border-b">
<div class="px-4 py-2 flex">
<GoogleIcon
class="text-xl text-success"
name="contact_mail"
/>
<div class="pl-3">
</div>
</div>
</div>
</ShowModal>
</template>

View File

@ -1,23 +1,25 @@
<?php <?php
use App\Http\Controllers\Admin\ScoreController;
use App\Http\Controllers\Admin\UserController; use App\Http\Controllers\Admin\UserController;
use App\Http\Controllers\Dashboard\HistoryLogController; use App\Http\Controllers\Dashboard\HistoryLogController;
use App\Http\Controllers\Dashboard\IndexController; use App\Http\Controllers\Dashboard\IndexController;
use App\Http\Controllers\Dashboard\NotificationController; use App\Http\Controllers\Dashboard\NotificationController;
use App\Http\Controllers\Admin\DepartmentController;
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 Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
/** /**
* Rutas generales/publicas * Rutas generales/publicas
* *
* 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
* *
* El dashboard es el panel de los usuarios de forma general * El dashboard es el panel de los usuarios de forma general
*/ */
Route::prefix('dashboard')->name('dashboard.')->middleware([ Route::prefix('dashboard')->name('dashboard.')->middleware([
@ -44,7 +46,7 @@
/** /**
* Rutas de administrador * Rutas de administrador
* *
* Estas ubicaciones son del administrador, sin embargo el desarrollador * Estas ubicaciones son del administrador, sin embargo el desarrollador
* puede acceder a ellas. * puede acceder a ellas.
*/ */
@ -52,7 +54,9 @@
'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('departments', DepartmentController::class);
Route::prefix('/users')->name('users.')->group(function() Route::prefix('/users')->name('users.')->group(function()
{ {
@ -64,7 +68,7 @@
/** /**
* Rutas solo del desarrollador * Rutas solo del desarrollador
* *
* Son ubicaciones o funciones que pueden llegar a ser muy sensibles en el sistema, por lo que * Son ubicaciones o funciones que pueden llegar a ser muy sensibles en el sistema, por lo que
* solo el desarrollador debe de ser capaz de modificarlas o actualizarlas. * solo el desarrollador debe de ser capaz de modificarlas o actualizarlas.
*/ */
@ -77,9 +81,9 @@
/** /**
* Elementos de la plantilla * Elementos de la plantilla
* *
* Estos son elementos que existen y pueden ser usados en la plantilla, vienen ejemplos de uso. * Estos son elementos que existen y pueden ser usados en la plantilla, vienen ejemplos de uso.
* *
* Estas rutas pueden ser comentadas o eliminadas cuando se finalice un proyecto. Por default estan ocultas * Estas rutas pueden ser comentadas o eliminadas cuando se finalice un proyecto. Por default estan ocultas
* en el dashboard. * en el dashboard.
*/ */
@ -89,4 +93,4 @@
config('jetstream.auth_session') config('jetstream.auth_session')
])->group(function () { ])->group(function () {
Route::get('/', [ExampleIndexController::class, 'index'])->name('index'); Route::get('/', [ExampleIndexController::class, 'index'])->name('index');
}); });