ADD: Historial de acciones (#5)
This commit is contained in:
parent
e42af7db7e
commit
b68bd0c27b
@ -2,7 +2,7 @@
|
||||
"name": "notsoweb.frontend",
|
||||
"copyright": "Notsoweb Software Inc.",
|
||||
"private": true,
|
||||
"version": "0.9.4",
|
||||
"version": "0.9.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
87
src/components/Holos/Paginable.vue
Normal file
87
src/components/Holos/Paginable.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<script setup>
|
||||
import GoogleIcon from '../Shared/GoogleIcon.vue';
|
||||
import Loader from '../Shared/Loader.vue';
|
||||
|
||||
/** Eventos */
|
||||
const emit = defineEmits([
|
||||
'send-pagination'
|
||||
]);
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
items: Object,
|
||||
processing: Boolean
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="pb-2">
|
||||
<div class="w-full overflow-hidden rounded-md shadow-lg">
|
||||
<div v-if="!processing" class="w-full overflow-x-auto">
|
||||
<template v-if="items?.total > 0">
|
||||
<slot
|
||||
name="body"
|
||||
:items="items?.data"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="$slots.empty">
|
||||
<slot name="empty" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex p-2 items-center justify-center">
|
||||
<p class="text-center text-page-t dark:text-page-dt">{{ $t('noRecords') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="flex items-center justify-center">
|
||||
<Loader />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<template v-if="items?.links">
|
||||
<div v-if="items.links.length > 3" class="flex w-full justify-end">
|
||||
<div class="flex w-full justify-end flex-wrap space-x-1 -mb-1">
|
||||
<template v-for="(link, k) in items.links" :key="k">
|
||||
<div v-if="link.url === null && k == 0"
|
||||
class="px-2 py-1 text-sm leading-4 text-gray-400 border rounded"
|
||||
>
|
||||
<GoogleIcon
|
||||
name="arrow_back"
|
||||
/>
|
||||
</div>
|
||||
<button v-else-if="k === 0" class="px-2 py-1 text-sm leading-4 border rounded"
|
||||
:class="{ 'bg-primary dark:bg-primary-dark text-white': link.active }"
|
||||
@click="$emit('send-pagination', link.url)"
|
||||
>
|
||||
<GoogleIcon
|
||||
name="arrow_back"
|
||||
/>
|
||||
</button>
|
||||
<div v-else-if="link.url === null && k == (items.links.length - 1)"
|
||||
class="px-2 py-1 text-sm leading-4 text-gray-400 border rounded"
|
||||
>
|
||||
<GoogleIcon
|
||||
name="arrow_forward"
|
||||
/>
|
||||
</div>
|
||||
<button v-else-if="k === (items.links.length - 1)" class="px-2 py-1 text-sm leading-4 border rounded"
|
||||
:class="{ 'bg-primary dark:bg-primary-dark text-white': link.active }"
|
||||
@click="$emit('send-pagination', link.url)"
|
||||
>
|
||||
<GoogleIcon
|
||||
name="arrow_forward"
|
||||
/>
|
||||
</button>
|
||||
<button v-else class="px-2 py-1 text-sm leading-4 border rounded"
|
||||
:class="{ 'bg-primary dark:bg-primary-dark text-white': link.active }"
|
||||
v-html="link.label"
|
||||
@click="$emit('send-pagination', link.url)"
|
||||
></button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
@ -70,7 +70,7 @@ defineProps({
|
||||
class="text-xs text-gray-400 truncate"
|
||||
/>
|
||||
<div v-else
|
||||
v-text="$t('system')"
|
||||
v-text="$t('system.title')"
|
||||
class="text-xs text-gray-400 truncate"
|
||||
/>
|
||||
</div>
|
||||
|
||||
91
src/components/Holos/Timeline/Item.vue
Normal file
91
src/components/Holos/Timeline/Item.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { getDate, getTime } from '@Controllers/DateController';
|
||||
|
||||
import PrimaryButton from '@Holos/Button/Primary.vue';
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
/** Eventos */
|
||||
const emit = defineEmits([
|
||||
'show',
|
||||
]);
|
||||
|
||||
const props = defineProps({
|
||||
event: Object,
|
||||
});
|
||||
|
||||
const icons = {
|
||||
created: 'add',
|
||||
updated: 'edit',
|
||||
deleted: 'delete',
|
||||
restored: 'restore',
|
||||
};
|
||||
|
||||
const colors = {
|
||||
created: 'primary',
|
||||
updated: 'primary',
|
||||
deleted: 'danger',
|
||||
restored: 'primary',
|
||||
};
|
||||
|
||||
const eventType = computed(() => {
|
||||
return props.event.event.split('.')[1];
|
||||
});
|
||||
|
||||
const bgColor = computed(() => {
|
||||
return `bg-${colors[eventType.value]} dark:bg-${colors[eventType.value]}-d text-${colors[eventType.value]}-t dark:text-${colors[eventType.value]}-t`;
|
||||
});
|
||||
|
||||
const borderColor = computed(() => {
|
||||
return `border-${colors[eventType.value]} dark:border-${colors[eventType.value]}-d`;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li class="border-l-2" :class="borderColor">
|
||||
<div class="relative flex w-full">
|
||||
<div class="absolute -left-3.5 top-7 h-0.5 w-8" :class="bgColor"></div>
|
||||
<div
|
||||
class="absolute -mt-3 -left-3.5 top-7 w-6 h-6 flex items-center justify-center rounded-full"
|
||||
:class="bgColor"
|
||||
@click="emit('show', event.data)"
|
||||
>
|
||||
<GoogleIcon
|
||||
:name="icons[eventType]"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full rounded-lg shadow-xl dark:shadow-page-dt dark:shadow-sm my-2 mx-4">
|
||||
<div class="flex justify-between p-2 rounded-t-lg" :class="bgColor">
|
||||
<span
|
||||
class="font-medium text-sm cursor-pointer"
|
||||
@click="emit('show', event.data)"
|
||||
>
|
||||
{{ $t('event')}}: <i class="underline">{{ event.event }}</i>
|
||||
</span>
|
||||
<span class="font-medium text-sm">
|
||||
{{ getDate(event.created_at) }}, {{ getTime(event.created_at) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<div class="flex flex-col justify-center items-center md:flex-row md:justify-start space-x-4">
|
||||
<div v-if="event.user" class="w-32">
|
||||
<div class="flex flex-col w-full justify-center items-center space-y-2">
|
||||
<img :src="event.user?.profile_photo_url" alt="Photo" class="w-24 h-24 rounded-full">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<div>
|
||||
<h4 class="font-semibold">{{ $t('description') }}:</h4>
|
||||
<p>{{ event.description }}.</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold">{{ $t('author') }}:</h4>
|
||||
<p>{{ event.user?.full_name ?? $t('system.title') }} <span v-if="event.user?.deleted_at" class="text-xs text-gray-500">({{ $t('deleted') }})</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
@ -69,9 +69,14 @@ export default {
|
||||
title: 'Cuenta',
|
||||
},
|
||||
actions:'Acciones',
|
||||
activity:'Actividad',
|
||||
add: 'Agregar',
|
||||
admin: {
|
||||
title: 'Administración',
|
||||
activity: {
|
||||
title: 'Historial de acciones',
|
||||
description: 'Historial de acciones realizadas por los usuarios en orden cronológico.'
|
||||
}
|
||||
},
|
||||
app: {
|
||||
theme: {
|
||||
@ -115,6 +120,7 @@ export default {
|
||||
notifySendVerification: 'Se ha enviado un nuevo enlace de verificación a su dirección de correo electrónico.',
|
||||
},
|
||||
},
|
||||
author:'Autor',
|
||||
code:'Código',
|
||||
contracted_at: 'Fecha contratación',
|
||||
cancel:'Cancelar',
|
||||
@ -347,7 +353,9 @@ export default {
|
||||
},
|
||||
startDate:'Fecha de inicio',
|
||||
status:'Estado',
|
||||
system:'Sistema',
|
||||
system:{
|
||||
title:'Núcleo de Holos',
|
||||
},
|
||||
target: {
|
||||
title: 'Meta',
|
||||
total: 'Meta total'
|
||||
@ -381,6 +389,10 @@ export default {
|
||||
unreaded:'No leído',
|
||||
user:'Usuario',
|
||||
users:{
|
||||
activity: {
|
||||
title: 'Actividad del usuario',
|
||||
description: 'Historial de acciones realizadas por el usuario.',
|
||||
},
|
||||
create:{
|
||||
title:'Crear usuario',
|
||||
description:'Permite crear nuevos usuarios. No olvides otorgarle roles para que pueda acceder a las partes del sistema deseados.',
|
||||
@ -388,6 +400,7 @@ export default {
|
||||
onError:'Ocurrió un error al crear el usuario'
|
||||
},
|
||||
deleted:'Usuario eliminado',
|
||||
remove: 'Remover usuario',
|
||||
edit: {
|
||||
title: 'Editar usuario'
|
||||
},
|
||||
@ -398,7 +411,7 @@ export default {
|
||||
},
|
||||
notFount:'Usuario no encontrado',
|
||||
password: {
|
||||
description:'Permite actualizar las contraseñas de los usuarios sobre escribiendola.',
|
||||
description:'Permite actualizar las contraseñas de los usuarios sobre escribiéndola.',
|
||||
title:'Actualizar contraseña',
|
||||
},
|
||||
roles: {
|
||||
@ -420,7 +433,7 @@ export default {
|
||||
title:'Usuarios',
|
||||
},
|
||||
version:'Versión',
|
||||
welcome: '<b>Bienvenido</b> {name}.',
|
||||
welcome: 'Bienvenido',
|
||||
workstation: 'Puesto de trabajo',
|
||||
workstations: {
|
||||
create: {
|
||||
|
||||
@ -32,7 +32,7 @@ onMounted(() => {
|
||||
<Link
|
||||
icon="monitoring"
|
||||
name="dashboard"
|
||||
to="index"
|
||||
to="dashboard.index"
|
||||
/>
|
||||
<Link
|
||||
icon="person"
|
||||
@ -56,6 +56,12 @@ onMounted(() => {
|
||||
name="roles.title"
|
||||
to="admin.roles.index"
|
||||
/>
|
||||
<Link
|
||||
v-if="hasPermission('activities.index')"
|
||||
icon="event"
|
||||
name="history.title"
|
||||
to="admin.activities.index"
|
||||
/>
|
||||
</Section>
|
||||
</template>
|
||||
<!-- Contenido -->
|
||||
|
||||
151
src/pages/Admin/Activities/Index.vue
Normal file
151
src/pages/Admin/Activities/Index.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { hasPermission } from '@Plugins/RolePermission';
|
||||
import { useSearcher } from '@Services/Api';
|
||||
import { apiTo } from './Module';
|
||||
|
||||
import ModalController from '@Controllers/ModalController.js';
|
||||
|
||||
import IconButton from '@Holos/Button/Icon.vue'
|
||||
import PrimaryButton from '@Holos/Button/Primary.vue';
|
||||
import Input from '@Holos/Form/Input.vue';
|
||||
import Header from '@Holos/PageHeader.vue';
|
||||
import Paginable from '@Holos/Paginable.vue';
|
||||
import Item from '@Holos/Timeline/Item.vue';
|
||||
import ShowView from './Modals/Event.vue';
|
||||
|
||||
/** Definidores */
|
||||
const vroute = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
/** Controladores */
|
||||
const Modal = new ModalController();
|
||||
|
||||
/** Propiedades */
|
||||
const showModal = ref(Modal.showModal);
|
||||
const modelModal = ref(Modal.modelModal);
|
||||
|
||||
const models = ref([]);
|
||||
|
||||
const filters = reactive({
|
||||
search: '',
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
user: ''
|
||||
});
|
||||
|
||||
/** Métodos */
|
||||
const searcher = useSearcher({
|
||||
url: apiTo('index'),
|
||||
filters,
|
||||
onSuccess: (r) => models.value = r.models,
|
||||
onError: () => models.value = []
|
||||
});
|
||||
|
||||
const clearFilters = () => {
|
||||
filters.search = '';
|
||||
filters.start_date = '';
|
||||
filters.end_date = '';
|
||||
|
||||
searcher.search();
|
||||
};
|
||||
|
||||
const clearUser = () => {
|
||||
router.replace({ query: {} });
|
||||
filters.user = '';
|
||||
|
||||
searcher.search();
|
||||
};
|
||||
|
||||
/** Ciclos */
|
||||
onMounted(() => {
|
||||
if(vroute.query?.user) {
|
||||
filters.user = vroute.query.user;
|
||||
}
|
||||
|
||||
searcher.search('');
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Header :title="$t('admin.activity.title')">
|
||||
<RouterLink v-if="filters.user && hasPermission('users.index')" :to="$view({ name: 'admin.users.index' })">
|
||||
<IconButton
|
||||
class="text-white"
|
||||
icon="arrow_back"
|
||||
:title="$t('return')"
|
||||
filled
|
||||
/>
|
||||
</RouterLink>
|
||||
</Header>
|
||||
<p class="mt-2">{{ $t('admin.activity.description') }}</p>
|
||||
|
||||
<div id="filters" class="grid gap-2 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 mt-4">
|
||||
<Input
|
||||
v-model="filters.search"
|
||||
title="event"
|
||||
@keyup.enter="searcher.search()"
|
||||
/>
|
||||
<Input
|
||||
v-model="filters.start_date"
|
||||
type="date"
|
||||
title="dates.start"
|
||||
/>
|
||||
<Input
|
||||
v-model="filters.end_date"
|
||||
title="dates.end"
|
||||
type="date"
|
||||
/>
|
||||
<div class="flex space-x-2 items-end">
|
||||
<PrimaryButton
|
||||
class="!w-full h-12"
|
||||
@click="searcher.search()"
|
||||
>
|
||||
{{ $t('search') }}
|
||||
</PrimaryButton>
|
||||
<PrimaryButton
|
||||
class="!w-full h-12"
|
||||
@click="clearFilters()"
|
||||
>
|
||||
{{ $t('clear') }}
|
||||
</PrimaryButton>
|
||||
<PrimaryButton
|
||||
v-if="filters.user"
|
||||
class="!w-full h-12"
|
||||
@click="clearUser()"
|
||||
>
|
||||
{{ $t('users.remove') }}
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pb-4">
|
||||
<Paginable
|
||||
:items="models"
|
||||
:processing="searcher.processing"
|
||||
@send-pagination="(page) => searcher.pagination(page)"
|
||||
>
|
||||
<template #body="{ items }">
|
||||
<ol class="ml-4">
|
||||
<template v-for="event in items">
|
||||
<Item
|
||||
:event="event"
|
||||
@show="Modal.switchShowModal(event)"
|
||||
/>
|
||||
</template>
|
||||
</ol>
|
||||
</template>
|
||||
</Paginable>
|
||||
</div>
|
||||
|
||||
<ShowView
|
||||
:show="showModal"
|
||||
:model="modelModal"
|
||||
@close="Modal.switchShowModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
62
src/pages/Admin/Activities/Modals/Event.vue
Normal file
62
src/pages/Admin/Activities/Modals/Event.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<script setup>
|
||||
import { getDateTime } from '@Controllers/DateController';
|
||||
|
||||
import Header from '@Holos/Modal/Elements/Header.vue';
|
||||
import ShowModal from '@Holos/Modal/Show.vue';
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
|
||||
/** Eventos */
|
||||
defineEmits([
|
||||
'close',
|
||||
]);
|
||||
|
||||
/** Propiedades */
|
||||
defineProps({
|
||||
show: Boolean,
|
||||
model: Object
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<ShowModal
|
||||
:show="show"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<Header
|
||||
:title="model.name"
|
||||
:subtitle="model.last_name"
|
||||
/>
|
||||
<div class="py-2 border-b">
|
||||
<div class="flex w-full px-4 py-2">
|
||||
<GoogleIcon
|
||||
class="text-xl text-success"
|
||||
name="contact_mail"
|
||||
/>
|
||||
<div class="pl-3 w-full">
|
||||
<p class="font-bold text-lg leading-none pb-2">
|
||||
{{ $t('details') }}
|
||||
</p>
|
||||
<div class="text-sm">
|
||||
<h4 class="font-semibold">{{ $t('event') }}:</h4>
|
||||
<p>{{ model.event }}</p>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<h4 class="font-semibold">{{ $t('description') }}:</h4>
|
||||
<p>{{ model.description }}</p>
|
||||
</div>
|
||||
<div class="flex w-full flex-col">
|
||||
<p class="font-semibold">
|
||||
{{ $t('changes') }}:
|
||||
</p>
|
||||
<div class="w-full text-xs p-2 border rounded-md">
|
||||
<pre>{{ model.data }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<h4 class="font-semibold">{{ $t('created_at') }}:</h4>
|
||||
<p>{{ getDateTime(model.created_at) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ShowModal>
|
||||
</template>
|
||||
71
src/pages/Admin/Activities/Modals/Show.vue
Normal file
71
src/pages/Admin/Activities/Modals/Show.vue
Normal file
@ -0,0 +1,71 @@
|
||||
<script setup>
|
||||
import { getDateTime } from '@Controllers/DateController';
|
||||
|
||||
import Header from '@Holos/Modal/Elements/Header.vue';
|
||||
import ShowModal from '@Holos/Modal/Show.vue';
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
/** Eventos */
|
||||
defineEmits([
|
||||
'close',
|
||||
]);
|
||||
|
||||
/** Propiedades */
|
||||
defineProps({
|
||||
show: Boolean,
|
||||
model: Object
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<ShowModal
|
||||
:show="show"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<Header
|
||||
:title="model.name"
|
||||
:subtitle="model.last_name"
|
||||
>
|
||||
<div class="flex w-full flex-col">
|
||||
<div class="flex w-full justify-center items-center">
|
||||
<img :src="model.profile_photo_url" alt="Photo" class="w-24 h-24 rounded-full">
|
||||
</div>
|
||||
</div>
|
||||
</Header>
|
||||
<div class="py-2 border-b">
|
||||
<div class="flex w-full px-4 py-2">
|
||||
<GoogleIcon
|
||||
class="text-xl text-success"
|
||||
name="contact_mail"
|
||||
/>
|
||||
<div class="pl-3">
|
||||
<p class="font-bold text-lg leading-none pb-2">
|
||||
{{ $t('details') }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ $t('name') }}: </b>
|
||||
{{ model.full_name }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ $t('phone') }}: </b>
|
||||
<a :href="`tel:${model.phone}`" target="_blank" class="hover:text-danger">
|
||||
{{ model.phone ?? '-' }}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ $t('email.title') }}: </b>
|
||||
<a :href="`mailto:${model.email}`" target="_blank" class="hover:text-danger">
|
||||
{{ model.email }}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ $t('created_at') }}: </b>
|
||||
{{ getDateTime(model.created_at) }}
|
||||
</p>
|
||||
<p>
|
||||
<b>{{ $t('updated_at') }}: </b>
|
||||
{{ getDateTime(model.updated_at) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ShowModal>
|
||||
</template>
|
||||
21
src/pages/Admin/Activities/Module.js
Normal file
21
src/pages/Admin/Activities/Module.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { lang } from '@Lang/i18n';
|
||||
import { hasPermission } from '@Plugins/RolePermission.js';
|
||||
|
||||
// Ruta API
|
||||
const apiTo = (name, params = {}) => route(`admin.activities.${name}`, params)
|
||||
|
||||
// Ruta visual
|
||||
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `admin.activities.${name}`, params, query })
|
||||
|
||||
// Obtener traducción del componente
|
||||
const transl = (str) => lang(`activities.${str}`)
|
||||
|
||||
// Determina si un usuario puede hacer algo no en base a los permisos
|
||||
const can = (permission) => hasPermission(`activities.${permission}`)
|
||||
|
||||
export {
|
||||
can,
|
||||
viewTo,
|
||||
apiTo,
|
||||
transl
|
||||
}
|
||||
@ -22,7 +22,7 @@ const modelModal = ref(Modal.modelModal);
|
||||
const models = ref([]);
|
||||
|
||||
const searcher = useSearcher({
|
||||
url: route('roles.index'),
|
||||
url: apiTo('index'),
|
||||
onSuccess: (r) => models.value = r.models,
|
||||
onError: () => models.value = []
|
||||
});
|
||||
@ -59,7 +59,7 @@ onMounted(() => {
|
||||
<div class="pt-2 w-full">
|
||||
<Table
|
||||
:items="models"
|
||||
@send-pagination="searcher.pagination"
|
||||
@send-pagination="(page) => searcher.pagination(page)"
|
||||
:processing="searcher.processing"
|
||||
>
|
||||
<template #head>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { onUpdated, ref } from 'vue';
|
||||
import { api } from '@Services/Api';
|
||||
import { apiTo } from '../Module';
|
||||
|
||||
import Header from '@Holos/Modal/Elements/Header.vue';
|
||||
import EditModal from '@Holos/Modal/Edit.vue';
|
||||
@ -22,7 +23,7 @@ const permissions = ref([]);
|
||||
|
||||
/** Métodos */
|
||||
function update() {
|
||||
api.put(route('roles.permissions', { role: props.model.id }), {
|
||||
api.put(apiTo('permissions', { role: props.model.id }), {
|
||||
data: {
|
||||
permissions: permissions.value
|
||||
},
|
||||
@ -40,7 +41,7 @@ onUpdated(() => {
|
||||
onSuccess: (r) => permissionTypes.value = r.models
|
||||
});
|
||||
|
||||
api.get(route('roles.permissions', { role: props.model.id }), {
|
||||
api.get(apiTo('permissions', { role: props.model.id }), {
|
||||
onSuccess: (r) => {
|
||||
if(r.permissions) {
|
||||
permissions.value = r.permissions.map(p => p.id);
|
||||
|
||||
@ -2,7 +2,7 @@ import { lang } from '@Lang/i18n';
|
||||
import { hasPermission } from '@Plugins/RolePermission.js';
|
||||
|
||||
// Ruta API
|
||||
const apiTo = (name, params = {}) => route(`roles.${name}`, params)
|
||||
const apiTo = (name, params = {}) => route(`admin.roles.${name}`, params)
|
||||
|
||||
// Ruta visual
|
||||
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `admin.roles.${name}`, params, query })
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { can, apiTo, viewTo } from './Module'
|
||||
import { useSearcher } from '@Services/Api';
|
||||
import { hasPermission } from '@Plugins/RolePermission';
|
||||
|
||||
import ModalController from '@Controllers/ModalController.js';
|
||||
|
||||
@ -22,7 +23,7 @@ const modelModal = ref(Modal.modelModal);
|
||||
const models = ref([]);
|
||||
|
||||
const searcher = useSearcher({
|
||||
url: route('users.index'),
|
||||
url: apiTo('index'),
|
||||
onSuccess: (r) => models.value = r.models,
|
||||
onError: () => models.value = []
|
||||
});
|
||||
@ -59,8 +60,8 @@ onMounted(() => {
|
||||
<div class="pt-2 w-full">
|
||||
<Table
|
||||
:items="models"
|
||||
@send-pagination="searcher.pagination"
|
||||
:processing="searcher.processing"
|
||||
@send-pagination="(page) => searcher.pagination(page)"
|
||||
>
|
||||
<template #head>
|
||||
<th v-text="$t('user')" />
|
||||
@ -132,6 +133,16 @@ onMounted(() => {
|
||||
:title="$t('setting')"
|
||||
/>
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
v-if="hasPermission('activities.index')"
|
||||
class="h-fit"
|
||||
:to="$view({ name: 'admin.activities.index', query: { user: model.id } })"
|
||||
>
|
||||
<IconButton
|
||||
icon="timeline"
|
||||
:title="$t('activity')"
|
||||
/>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -2,7 +2,7 @@ import { lang } from '@Lang/i18n';
|
||||
import { hasPermission } from '@Plugins/RolePermission.js';
|
||||
|
||||
// Ruta API
|
||||
const apiTo = (name, params = {}) => route(`users.${name}`, params)
|
||||
const apiTo = (name, params = {}) => route(`admin.users.${name}`, params)
|
||||
|
||||
// Ruta visual
|
||||
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `admin.users.${name}`, params, query })
|
||||
|
||||
@ -44,6 +44,18 @@ const changelogs = [
|
||||
'FIX: Tooltip de botones en tablas.'
|
||||
],
|
||||
date: '2024-12-28'
|
||||
},
|
||||
{
|
||||
version: '0.9.5',
|
||||
details: [
|
||||
'ADD: Historial de acciones general.',
|
||||
'ADD: Historial de acciones por usuario.',
|
||||
'FIX: Paginación con filtros.',
|
||||
'UPDATE: La ruta / ahora redirige a /dashboard en caso de que se desarrolle un frontend publico.',
|
||||
'UPDATE: Se agregaron los elementos administrados en /admin',
|
||||
'UPDATE: Redirección a dashboard al iniciar sesión.'
|
||||
],
|
||||
date: '2025-01-03'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
@ -5,5 +5,5 @@ import PageHeader from '@Holos/PageHeader.vue';
|
||||
|
||||
<template>
|
||||
<PageHeader title="Dashboard" />
|
||||
<p v-html="$t('welcome', { name: $page.user.name })"></p>
|
||||
<p><b>{{ $t('welcome') }}</b>, {{ $page.user.name }}.</p>
|
||||
</template>
|
||||
@ -9,4 +9,5 @@
|
||||
<p class="bg-secondary"></p>
|
||||
<p class="hover:bg-primary/80 dark:hover:bg-primary-d/80"></p>
|
||||
<p class="bg-secondary-d"></p>
|
||||
<p class="bg-danger border-danger"></p>
|
||||
</template>
|
||||
@ -15,6 +15,11 @@ const router = createRouter({
|
||||
{
|
||||
path: '/',
|
||||
name: 'index',
|
||||
redirect: '/dashboard'
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard.index',
|
||||
component: () => import('@Pages/Dashboard/Index.vue')
|
||||
}, {
|
||||
path: '/profile',
|
||||
@ -89,6 +94,17 @@ const router = createRouter({
|
||||
component: () => import('@Pages/Admin/Roles/Edit.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'activities',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'admin.activities.index',
|
||||
beforeEnter: (to, from, next) => can(next, 'activities.index'),
|
||||
component: () => import('@Pages/Admin/Activities/Index.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -537,7 +537,6 @@ const useSearcher = (options = {
|
||||
this.processing = false;
|
||||
},
|
||||
pagination(url, filter = {}) {
|
||||
console.log(url, filter)
|
||||
this.load({
|
||||
url,
|
||||
filters : {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user