ADD: Historial de acciones (#5)
This commit is contained in:
parent
e42af7db7e
commit
b68bd0c27b
@ -2,7 +2,7 @@
|
|||||||
"name": "notsoweb.frontend",
|
"name": "notsoweb.frontend",
|
||||||
"copyright": "Notsoweb Software Inc.",
|
"copyright": "Notsoweb Software Inc.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.9.4",
|
"version": "0.9.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"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"
|
class="text-xs text-gray-400 truncate"
|
||||||
/>
|
/>
|
||||||
<div v-else
|
<div v-else
|
||||||
v-text="$t('system')"
|
v-text="$t('system.title')"
|
||||||
class="text-xs text-gray-400 truncate"
|
class="text-xs text-gray-400 truncate"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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',
|
title: 'Cuenta',
|
||||||
},
|
},
|
||||||
actions:'Acciones',
|
actions:'Acciones',
|
||||||
|
activity:'Actividad',
|
||||||
add: 'Agregar',
|
add: 'Agregar',
|
||||||
admin: {
|
admin: {
|
||||||
title: 'Administración',
|
title: 'Administración',
|
||||||
|
activity: {
|
||||||
|
title: 'Historial de acciones',
|
||||||
|
description: 'Historial de acciones realizadas por los usuarios en orden cronológico.'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
app: {
|
app: {
|
||||||
theme: {
|
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.',
|
notifySendVerification: 'Se ha enviado un nuevo enlace de verificación a su dirección de correo electrónico.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
author:'Autor',
|
||||||
code:'Código',
|
code:'Código',
|
||||||
contracted_at: 'Fecha contratación',
|
contracted_at: 'Fecha contratación',
|
||||||
cancel:'Cancelar',
|
cancel:'Cancelar',
|
||||||
@ -347,7 +353,9 @@ export default {
|
|||||||
},
|
},
|
||||||
startDate:'Fecha de inicio',
|
startDate:'Fecha de inicio',
|
||||||
status:'Estado',
|
status:'Estado',
|
||||||
system:'Sistema',
|
system:{
|
||||||
|
title:'Núcleo de Holos',
|
||||||
|
},
|
||||||
target: {
|
target: {
|
||||||
title: 'Meta',
|
title: 'Meta',
|
||||||
total: 'Meta total'
|
total: 'Meta total'
|
||||||
@ -381,6 +389,10 @@ export default {
|
|||||||
unreaded:'No leído',
|
unreaded:'No leído',
|
||||||
user:'Usuario',
|
user:'Usuario',
|
||||||
users:{
|
users:{
|
||||||
|
activity: {
|
||||||
|
title: 'Actividad del usuario',
|
||||||
|
description: 'Historial de acciones realizadas por el usuario.',
|
||||||
|
},
|
||||||
create:{
|
create:{
|
||||||
title:'Crear usuario',
|
title:'Crear usuario',
|
||||||
description:'Permite crear nuevos usuarios. No olvides otorgarle roles para que pueda acceder a las partes del sistema deseados.',
|
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'
|
onError:'Ocurrió un error al crear el usuario'
|
||||||
},
|
},
|
||||||
deleted:'Usuario eliminado',
|
deleted:'Usuario eliminado',
|
||||||
|
remove: 'Remover usuario',
|
||||||
edit: {
|
edit: {
|
||||||
title: 'Editar usuario'
|
title: 'Editar usuario'
|
||||||
},
|
},
|
||||||
@ -398,7 +411,7 @@ export default {
|
|||||||
},
|
},
|
||||||
notFount:'Usuario no encontrado',
|
notFount:'Usuario no encontrado',
|
||||||
password: {
|
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',
|
title:'Actualizar contraseña',
|
||||||
},
|
},
|
||||||
roles: {
|
roles: {
|
||||||
@ -420,7 +433,7 @@ export default {
|
|||||||
title:'Usuarios',
|
title:'Usuarios',
|
||||||
},
|
},
|
||||||
version:'Versión',
|
version:'Versión',
|
||||||
welcome: '<b>Bienvenido</b> {name}.',
|
welcome: 'Bienvenido',
|
||||||
workstation: 'Puesto de trabajo',
|
workstation: 'Puesto de trabajo',
|
||||||
workstations: {
|
workstations: {
|
||||||
create: {
|
create: {
|
||||||
|
|||||||
@ -32,7 +32,7 @@ onMounted(() => {
|
|||||||
<Link
|
<Link
|
||||||
icon="monitoring"
|
icon="monitoring"
|
||||||
name="dashboard"
|
name="dashboard"
|
||||||
to="index"
|
to="dashboard.index"
|
||||||
/>
|
/>
|
||||||
<Link
|
<Link
|
||||||
icon="person"
|
icon="person"
|
||||||
@ -56,6 +56,12 @@ onMounted(() => {
|
|||||||
name="roles.title"
|
name="roles.title"
|
||||||
to="admin.roles.index"
|
to="admin.roles.index"
|
||||||
/>
|
/>
|
||||||
|
<Link
|
||||||
|
v-if="hasPermission('activities.index')"
|
||||||
|
icon="event"
|
||||||
|
name="history.title"
|
||||||
|
to="admin.activities.index"
|
||||||
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
</template>
|
</template>
|
||||||
<!-- Contenido -->
|
<!-- 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 models = ref([]);
|
||||||
|
|
||||||
const searcher = useSearcher({
|
const searcher = useSearcher({
|
||||||
url: route('roles.index'),
|
url: apiTo('index'),
|
||||||
onSuccess: (r) => models.value = r.models,
|
onSuccess: (r) => models.value = r.models,
|
||||||
onError: () => models.value = []
|
onError: () => models.value = []
|
||||||
});
|
});
|
||||||
@ -59,7 +59,7 @@ onMounted(() => {
|
|||||||
<div class="pt-2 w-full">
|
<div class="pt-2 w-full">
|
||||||
<Table
|
<Table
|
||||||
:items="models"
|
:items="models"
|
||||||
@send-pagination="searcher.pagination"
|
@send-pagination="(page) => searcher.pagination(page)"
|
||||||
:processing="searcher.processing"
|
:processing="searcher.processing"
|
||||||
>
|
>
|
||||||
<template #head>
|
<template #head>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onUpdated, ref } from 'vue';
|
import { onUpdated, ref } from 'vue';
|
||||||
import { api } from '@Services/Api';
|
import { api } from '@Services/Api';
|
||||||
|
import { apiTo } from '../Module';
|
||||||
|
|
||||||
import Header from '@Holos/Modal/Elements/Header.vue';
|
import Header from '@Holos/Modal/Elements/Header.vue';
|
||||||
import EditModal from '@Holos/Modal/Edit.vue';
|
import EditModal from '@Holos/Modal/Edit.vue';
|
||||||
@ -22,7 +23,7 @@ const permissions = ref([]);
|
|||||||
|
|
||||||
/** Métodos */
|
/** Métodos */
|
||||||
function update() {
|
function update() {
|
||||||
api.put(route('roles.permissions', { role: props.model.id }), {
|
api.put(apiTo('permissions', { role: props.model.id }), {
|
||||||
data: {
|
data: {
|
||||||
permissions: permissions.value
|
permissions: permissions.value
|
||||||
},
|
},
|
||||||
@ -40,7 +41,7 @@ onUpdated(() => {
|
|||||||
onSuccess: (r) => permissionTypes.value = r.models
|
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) => {
|
onSuccess: (r) => {
|
||||||
if(r.permissions) {
|
if(r.permissions) {
|
||||||
permissions.value = r.permissions.map(p => p.id);
|
permissions.value = r.permissions.map(p => p.id);
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { lang } from '@Lang/i18n';
|
|||||||
import { hasPermission } from '@Plugins/RolePermission.js';
|
import { hasPermission } from '@Plugins/RolePermission.js';
|
||||||
|
|
||||||
// Ruta API
|
// Ruta API
|
||||||
const apiTo = (name, params = {}) => route(`roles.${name}`, params)
|
const apiTo = (name, params = {}) => route(`admin.roles.${name}`, params)
|
||||||
|
|
||||||
// Ruta visual
|
// Ruta visual
|
||||||
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `admin.roles.${name}`, params, query })
|
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `admin.roles.${name}`, params, query })
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { can, apiTo, viewTo } from './Module'
|
import { can, apiTo, viewTo } from './Module'
|
||||||
import { useSearcher } from '@Services/Api';
|
import { useSearcher } from '@Services/Api';
|
||||||
|
import { hasPermission } from '@Plugins/RolePermission';
|
||||||
|
|
||||||
import ModalController from '@Controllers/ModalController.js';
|
import ModalController from '@Controllers/ModalController.js';
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ const modelModal = ref(Modal.modelModal);
|
|||||||
const models = ref([]);
|
const models = ref([]);
|
||||||
|
|
||||||
const searcher = useSearcher({
|
const searcher = useSearcher({
|
||||||
url: route('users.index'),
|
url: apiTo('index'),
|
||||||
onSuccess: (r) => models.value = r.models,
|
onSuccess: (r) => models.value = r.models,
|
||||||
onError: () => models.value = []
|
onError: () => models.value = []
|
||||||
});
|
});
|
||||||
@ -31,7 +32,7 @@ const searcher = useSearcher({
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
searcher.search();
|
searcher.search();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
@ -59,8 +60,8 @@ onMounted(() => {
|
|||||||
<div class="pt-2 w-full">
|
<div class="pt-2 w-full">
|
||||||
<Table
|
<Table
|
||||||
:items="models"
|
:items="models"
|
||||||
@send-pagination="searcher.pagination"
|
|
||||||
:processing="searcher.processing"
|
:processing="searcher.processing"
|
||||||
|
@send-pagination="(page) => searcher.pagination(page)"
|
||||||
>
|
>
|
||||||
<template #head>
|
<template #head>
|
||||||
<th v-text="$t('user')" />
|
<th v-text="$t('user')" />
|
||||||
@ -132,6 +133,16 @@ onMounted(() => {
|
|||||||
:title="$t('setting')"
|
:title="$t('setting')"
|
||||||
/>
|
/>
|
||||||
</RouterLink>
|
</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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { lang } from '@Lang/i18n';
|
|||||||
import { hasPermission } from '@Plugins/RolePermission.js';
|
import { hasPermission } from '@Plugins/RolePermission.js';
|
||||||
|
|
||||||
// Ruta API
|
// Ruta API
|
||||||
const apiTo = (name, params = {}) => route(`users.${name}`, params)
|
const apiTo = (name, params = {}) => route(`admin.users.${name}`, params)
|
||||||
|
|
||||||
// Ruta visual
|
// Ruta visual
|
||||||
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `admin.users.${name}`, params, query })
|
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `admin.users.${name}`, params, query })
|
||||||
|
|||||||
@ -44,6 +44,18 @@ const changelogs = [
|
|||||||
'FIX: Tooltip de botones en tablas.'
|
'FIX: Tooltip de botones en tablas.'
|
||||||
],
|
],
|
||||||
date: '2024-12-28'
|
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>
|
</script>
|
||||||
|
|||||||
@ -5,5 +5,5 @@ import PageHeader from '@Holos/PageHeader.vue';
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PageHeader title="Dashboard" />
|
<PageHeader title="Dashboard" />
|
||||||
<p v-html="$t('welcome', { name: $page.user.name })"></p>
|
<p><b>{{ $t('welcome') }}</b>, {{ $page.user.name }}.</p>
|
||||||
</template>
|
</template>
|
||||||
@ -9,4 +9,5 @@
|
|||||||
<p class="bg-secondary"></p>
|
<p class="bg-secondary"></p>
|
||||||
<p class="hover:bg-primary/80 dark:hover:bg-primary-d/80"></p>
|
<p class="hover:bg-primary/80 dark:hover:bg-primary-d/80"></p>
|
||||||
<p class="bg-secondary-d"></p>
|
<p class="bg-secondary-d"></p>
|
||||||
|
<p class="bg-danger border-danger"></p>
|
||||||
</template>
|
</template>
|
||||||
@ -15,6 +15,11 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'index',
|
name: 'index',
|
||||||
|
redirect: '/dashboard'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/dashboard',
|
||||||
|
name: 'dashboard.index',
|
||||||
component: () => import('@Pages/Dashboard/Index.vue')
|
component: () => import('@Pages/Dashboard/Index.vue')
|
||||||
}, {
|
}, {
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
@ -89,6 +94,17 @@ const router = createRouter({
|
|||||||
component: () => import('@Pages/Admin/Roles/Edit.vue')
|
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;
|
this.processing = false;
|
||||||
},
|
},
|
||||||
pagination(url, filter = {}) {
|
pagination(url, filter = {}) {
|
||||||
console.log(url, filter)
|
|
||||||
this.load({
|
this.load({
|
||||||
url,
|
url,
|
||||||
filters : {
|
filters : {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user