ADD: Breadcrum y modals

- FIX: Token de sesión movido a localStorage
- UPDATE: Dependencias
- UPDATE: Funcionamiento de los modals
- ADD: Breadcrumbs
This commit is contained in:
Moisés Cortés C. 2025-07-14 10:12:37 -06:00
parent c3965ef4cf
commit c453697d4e
32 changed files with 1059 additions and 992 deletions

887
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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.10", "version": "0.9.12",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -0,0 +1,5 @@
<template>
<nav class="flex items-center py-1 gap-0.5">
<slot />
</nav>
</template>

View File

@ -0,0 +1,55 @@
<script setup>
import GoogleIcon from '@Shared/GoogleIcon.vue'
import { useRouter } from 'vue-router';
/** Definidores */
const router = useRouter();
/** Propiedades */
const props = defineProps({
name: String,
icon: String,
route: Object,
active: Boolean,
})
/** Métodos */
const handleClick = () => {
if (props.active) {
return;
}
router.push(props.route);
}
</script>
<template>
<button
v-if="!active"
:to="route"
class="inline-flex items-center gap-1.5 text-sm duration-300 ease-in p-1 cursor-pointer"
@click="handleClick"
>
<GoogleIcon
:name="icon"
class="text-sm"
/>
<span class="text-sm font-semibold hover:underline" v-text="name" />
</button>
<span v-if="!active" class="inline-block text-sm select-none pointer-events-none opacity-50">
<GoogleIcon
name="arrow_forward_ios"
class="text-xs font-semibold"
/>
</span>
<div
v-if="active"
class="inline-flex items-center gap-1.5 text-sm duration-300 ease-in p-1"
>
<GoogleIcon
:name="icon"
class="text-sm"
/>
<span class="font-semibold text-sm underline" v-text="name" />
</div>
</template>

View File

@ -0,0 +1,13 @@
<script setup>
import GoogleIcon from '@Shared/GoogleIcon.vue'
</script>
<template>
<span class="inline-block mx-1 text-sm select-none pointer-events-none opacity-50">
<GoogleIcon
name="arrow_forward_ios"
class="text-xs font-semibold"
/>
</span>
</template>

View File

@ -1,9 +1,9 @@
<script setup> <script setup>
import { ref, nextTick } from 'vue'; import { ref, nextTick } from 'vue';
import { api, useForm } from '@Services/Api'; import { useForm } from '@Services/Api';
import Input from './Form/Input.vue'; import Input from './Form/Input.vue';
import DialogModal from './DialogModal.vue'; import DialogModal from './Modal/Elements/Base.vue';
import PrimaryButton from './Button/Primary.vue'; import PrimaryButton from './Button/Primary.vue';
import SecondaryButton from './Button/Secondary.vue'; import SecondaryButton from './Button/Secondary.vue';

View File

@ -1,45 +0,0 @@
<script setup>
import Modal from './Modal.vue';
const emit = defineEmits(['close']);
defineProps({
show: {
type: Boolean,
default: false,
},
maxWidth: {
type: String,
default: '2xl',
},
closeable: {
type: Boolean,
default: true,
},
});
const close = () => {
emit('close');
};
</script>
<template>
<Modal
:show="show"
:max-width="maxWidth"
:closeable="closeable"
@close="close"
>
<div>
<div class="text-lg px-4 font-medium">
<slot name="title" />
</div>
<div class="text-sm">
<slot name="content" />
</div>
</div>
<div class="flex flex-row justify-center p-2 text-end">
<slot name="footer" />
</div>
</Modal>
</template>

View File

@ -1,7 +1,9 @@
<script setup> <script setup>
import { ref } from 'vue';
import ModalBase from './Elements/Base.vue';
import DangerButton from '../Button/Danger.vue'; import DangerButton from '../Button/Danger.vue';
import SecondaryButton from '../Button/Secondary.vue'; import SecondaryButton from '../Button/Secondary.vue';
import DialogModal from '../DialogModal.vue';
/** Eventos */ /** Eventos */
defineEmits([ defineEmits([
@ -11,16 +13,27 @@ defineEmits([
/** Propiedades */ /** Propiedades */
const props = defineProps({ const props = defineProps({
show: Boolean,
title: { title: {
default: Lang('delete.title'), default: Lang('delete.title'),
type: String type: String
} }
}); });
/** Referencias */
const modalRef = ref(null);
/** Exposiciones */
defineExpose({
open: () => modalRef.value.open(),
close: () => modalRef.value.close()
});
</script> </script>
<template> <template>
<DialogModal :show="show"> <ModalBase
ref="modalRef"
@close="$emit('close')"
>
<template #title> <template #title>
<p <p
class="font-bold text-xl" class="font-bold text-xl"
@ -49,9 +62,9 @@ const props = defineProps({
/> />
<SecondaryButton <SecondaryButton
v-text="$t('cancel')" v-text="$t('cancel')"
@click="$emit('close')" @click="modalRef.close()"
/> />
</div> </div>
</template> </template>
</DialogModal> </ModalBase>
</template> </template>

View File

@ -1,51 +0,0 @@
<script setup>
import PrimaryButton from '../Button/Primary.vue';
import SecondaryButton from '../Button/Secondary.vue';
import DialogModal from '../DialogModal.vue';
/** Eventos */
const emit = defineEmits([
'close',
'update'
]);
/** Propiedades */
const props = defineProps({
show: Boolean,
title: {
default: Lang('edit'),
type: String
}
});
</script>
<template>
<DialogModal :show="show">
<template #title>
<p
class="font-bold text-xl"
v-text="title"
/>
</template>
<template #content>
<div class="w-full right-0">
<div class="overflow-hidden shadow-lg">
<slot />
</div>
</div>
</template>
<template #footer>
<div class="space-x-2">
<slot name="buttons" />
<PrimaryButton
v-text="$t('update')"
@click="$emit('update')"
/>
<SecondaryButton
v-text="$t('close')"
@click="$emit('close')"
/>
</div>
</template>
</DialogModal>
</template>

View File

@ -1,40 +1,20 @@
<script setup> <script setup>
import { computed, onMounted, onUnmounted, watch } from 'vue'; import { computed, ref, watch } from 'vue';
/** Eventos */ /** Eventos */
const emit = defineEmits([ const emit = defineEmits(['close']);
'close'
]);
/** Propiedades */ /** Propiedades */
const props = defineProps({ const props = defineProps({
closeable: {
default: true,
type: Boolean
},
maxWidth: { maxWidth: {
default: '2xl', default: '2xl',
type: String type: String
}, }
show: {
default: false,
type: Boolean
},
}); });
const show = ref(false);
/** Métodos */ /** Métodos */
const close = () => {
if (props.closeable) {
emit('close');
}
};
const closeOnEscape = (e) => {
if (e.key === 'Escape' && props.show) {
close();
}
};
const maxWidthClass = computed(() => { const maxWidthClass = computed(() => {
return { return {
'sm': 'sm:max-w-sm', 'sm': 'sm:max-w-sm',
@ -42,29 +22,45 @@ const maxWidthClass = computed(() => {
'lg': 'sm:max-w-lg', 'lg': 'sm:max-w-lg',
'xl': 'sm:max-w-xl', 'xl': 'sm:max-w-xl',
'2xl': 'sm:max-w-2xl', '2xl': 'sm:max-w-2xl',
'3xl': 'sm:max-w-3xl',
'4xl': 'sm:max-w-4xl',
'5xl': 'sm:max-w-5xl',
'6xl': 'sm:max-w-6xl',
'7xl': 'sm:max-w-7xl',
}[props.maxWidth]; }[props.maxWidth];
}); });
/** Observadores */ /** Observadores */
watch(() => props.show, () => { watch(() => show, () => {
if (props.show) { if (show.value) {
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
} else { } else {
document.body.style.overflow = null; document.body.style.overflow = null;
} }
}); });
/** Ciclos */ /** Exposiciones */
onMounted(() => document.addEventListener('keydown', closeOnEscape)); defineExpose({
open: () => show.value = true,
onUnmounted(() => { close: () => {
document.removeEventListener('keydown', closeOnEscape); show.value = false;
document.body.style.overflow = null; emit('close');
}
}); });
</script> </script>
<template> <template>
<teleport to="body"> <teleport to="body">
<transition
enter-active-class="ease-out duration-300"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="ease-in duration-300"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div v-show="show" class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 bg-primary/50 z-50 transition-all"></div>
</transition>
<transition <transition
enter-active-class="ease-out duration-300" enter-active-class="ease-out duration-300"
enter-from-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enter-from-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
@ -73,13 +69,23 @@ onUnmounted(() => {
leave-from-class="opacity-100 translate-y-0 sm:scale-100" leave-from-class="opacity-100 translate-y-0 sm:scale-100"
leave-to-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leave-to-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
> >
<div v-show="show" class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 bg-primary/90 z-50 transition-all" scroll-region> <div v-show="show" class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50 transition-all" scroll-region>
<div <div
v-show="show" v-show="show"
class="mb-6 bg-page text-page-t dark:bg-page-d dark:text-page-dt rounded-sm overflow-hidden shadow-xl transform transition-all sm:w-full sm:mx-auto" class="mb-6 bg-page text-page-t dark:bg-page-d dark:text-page-dt rounded-sm overflow-hidden shadow-xl transform transition-all sm:w-full sm:mx-auto"
:class="maxWidthClass" :class="maxWidthClass"
> >
<slot v-if="show" /> <div class="flex flex-col">
<div class="text-lg px-4 font-medium">
<slot name="title" />
</div>
<div class="text-sm">
<slot name="content" />
</div>
</div>
<div class="flex flex-row justify-center p-2 text-end">
<slot name="footer" />
</div>
</div> </div>
</div> </div>
</transition> </transition>

View File

@ -1,24 +1,32 @@
<script setup> <script setup>
import { ref } from 'vue';
import ModalBase from './Elements/Base.vue';
import SecondaryButton from '../Button/Secondary.vue'; import SecondaryButton from '../Button/Secondary.vue';
import PrimaryButton from '../Button/Primary.vue';
import DialogModal from '../DialogModal.vue';
/** Eventos */ /** Eventos */
const emit = defineEmits([ const emit = defineEmits(['close']);
'close',
'edit'
]);
/** Propiedades */ /** Propiedades */
const props = defineProps({ const props = defineProps({
editable: Boolean,
show: Boolean,
title: String title: String
}); });
/** Referencias */
const modalRef = ref(null);
/** Exposiciones */
defineExpose({
open: () => modalRef.value.open(),
close: () => modalRef.value.close()
});
</script> </script>
<template> <template>
<DialogModal :show="show"> <ModalBase
ref="modalRef"
@close="emit('close')"
>
<template #title> <template #title>
<p <p
class="font-bold text-xl" class="font-bold text-xl"
@ -35,15 +43,11 @@ const props = defineProps({
<template #footer> <template #footer>
<div class="space-x-2"> <div class="space-x-2">
<slot name="buttons" /> <slot name="buttons" />
<PrimaryButton v-if="editable"
v-text="$t('update')"
@click="$emit('edit')"
/>
<SecondaryButton <SecondaryButton
v-text="$t('close')" v-text="$t('close')"
@click="$emit('close')" @click="modalRef.close()"
/> />
</div> </div>
</template> </template>
</DialogModal> </ModalBase>
</template> </template>

View File

@ -1,4 +1,5 @@
<script setup> <script setup>
import { ref } from 'vue';
import { api } from '@Services/Api.js'; import { api } from '@Services/Api.js';
import DestroyModal from '../Destroy.vue'; import DestroyModal from '../Destroy.vue';
@ -6,14 +7,13 @@ import Header from '../Elements/Header.vue';
/** Eventos */ /** Eventos */
const emit = defineEmits([ const emit = defineEmits([
'open',
'close', 'close',
'update' 'update'
]); ]);
/** Propiedades */ /** Propiedades */
const props = defineProps({ const props = defineProps({
model: Object,
show: Boolean,
to: Function, to: Function,
title: { title: {
type: String, type: String,
@ -25,25 +25,41 @@ const props = defineProps({
} }
}); });
const model = ref({});
const modalRef = ref(null);
/** Métodos */ /** Métodos */
const destroy = (id) => api.delete(props.to(id), { const destroy = () => {
api.delete(props.to(model.value.id), {
onSuccess: () => { onSuccess: () => {
Notify.success(Lang('deleted')); Notify.success(Lang('deleted'));
emit('close');
emit('update'); emit('update');
}, },
onError: () => { onError: () => {
Notify.info(Lang('notFound')); Notify.info(Lang('notFound'));
emit('close'); },
onFinish: () => {
modalRef.value.close();
}
});
}
/** Exposiciones */
defineExpose({
open: (modelData) => {
model.value = modelData;
modalRef.value.open();
emit('open')
} }
}); });
</script> </script>
<template> <template>
<DestroyModal <DestroyModal
:show="show" ref="modalRef"
@close="$emit('close')" @close="$emit('close')"
@destroy="destroy(model.id)" @destroy="destroy"
> >
<Header <Header
:title="model[title]" :title="model[title]"

View File

@ -1,21 +1,34 @@
<script setup> <script setup>
import { RouterLink } from 'vue-router'; import { RouterLink, useRoute } from 'vue-router';
import { breadcrumbItem, breadcrumbMeta } from '@Controllers/BreadcrumbController.js';
import IconButton from '@Holos/Button/Icon.vue' import IconButton from '@Holos/Button/Icon.vue'
import BreadcrumbContainer from '@Holos/Breadcrumb/Container.vue'
import BreadcrumbItem from '@Holos/Breadcrumb/Item.vue'
defineProps({ /** Definidores */
const vroute = useRoute();
const props = defineProps({
title: String title: String
}); });
const breadcrumbs = breadcrumbMeta(vroute);
</script> </script>
<template> <template>
<div v-if="title" class="flex w-full justify-center"> <div>
<h2 <BreadcrumbContainer>
class="font-bold text-xl uppercase" <template v-for="(item, index) in breadcrumbs" :key="item.name">
v-text="title" <BreadcrumbItem
:name="item.name"
:icon="item.icon"
:route="item.route"
:active="index === breadcrumbs.length - 1"
/> />
</div> </template>
</BreadcrumbContainer>
<div class="flex w-full justify-end py-[0.31rem] mb-2 border-y-2 border-page-t dark:border-page-dt"> <div class="flex w-full justify-end py-[0.31rem] mb-2 border-y-2 border-page-t dark:border-page-dt">
<div id="buttons" class="flex items-center space-x-1 text-sm"> <div id="buttons" class="flex items-center space-x-1 text-sm">
<slot /> <slot />
@ -23,10 +36,11 @@ defineProps({
<IconButton <IconButton
:title="$t('home')" :title="$t('home')"
class="text-white" class="text-white"
icon="home" icon="refresh"
filled filled
/> />
</RouterLink> </RouterLink>
</div> </div>
</div> </div>
</div>
</template> </template>

View File

@ -1,8 +1,15 @@
<script setup> <script setup>
import { ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { breadcrumbMeta } from '@Controllers/BreadcrumbController.js';
import IconButton from '@Holos/Button/Icon.vue' import IconButton from '@Holos/Button/Icon.vue'
import GoogleIcon from '@Shared/GoogleIcon.vue'; import GoogleIcon from '@Shared/GoogleIcon.vue';
import BreadcrumbContainer from '@Holos/Breadcrumb/Container.vue'
import BreadcrumbItem from '@Holos/Breadcrumb/Item.vue'
/** Definidores */
const vroute = useRoute();
/** Eventos */ /** Eventos */
const emit = defineEmits([ const emit = defineEmits([
@ -18,6 +25,7 @@ const props = defineProps({
} }
}) })
const breadcrumbs = breadcrumbMeta(vroute);
const query = ref(''); const query = ref('');
/** Métodos */ /** Métodos */
@ -30,14 +38,20 @@ const clear = () => {
search(); search();
} }
</script> </script>
<template> <template>
<div v-if="title" class="flex w-full justify-center"> <BreadcrumbContainer>
<h2 <template v-for="(item, index) in breadcrumbs" :key="item.name">
class="font-bold text-xl uppercase" <BreadcrumbItem
v-text="title" :name="item.name"
:icon="item.icon"
:route="item.route"
:active="index === breadcrumbs.length - 1"
/> />
</div> </template>
</BreadcrumbContainer>
<div class="flex w-full justify-between items-center border-y-2 border-page-t dark:border-page-dt"> <div class="flex w-full justify-between items-center border-y-2 border-page-t dark:border-page-dt">
<div> <div>
<div class="relative py-1 z-0"> <div class="relative py-1 z-0">

View File

@ -4,30 +4,24 @@ import { RouterLink } from 'vue-router';
import useNotificationSidebar from '@Stores/NotificationSidebar' import useNotificationSidebar from '@Stores/NotificationSidebar'
import useNotifier from '@Stores/Notifier' import useNotifier from '@Stores/Notifier'
import ModalController from '@Controllers/ModalController.js';
import GoogleIcon from '@Shared/GoogleIcon.vue'; import GoogleIcon from '@Shared/GoogleIcon.vue';
import Item from './Notification/Item.vue';
import PrimaryButton from '@Holos/Button/Primary.vue'; import PrimaryButton from '@Holos/Button/Primary.vue';
import ShowView from '@Holos/Skeleton/Sidebar/Notification/Show.vue'; import ShowModal from './Notification/Show.vue';
import Item from './Notification/Item.vue';
/** Eventos */
const emit = defineEmits(['open']);
/** Definidores */ /** Definidores */
const notifier = useNotifier(); const notifier = useNotifier();
const notificationSidebar = useNotificationSidebar() const notificationSidebar = useNotificationSidebar()
/** Controladores */
const Modal = new ModalController();
/** Eventos */
const emit = defineEmits(['open']);
/** Propiedades */ /** Propiedades */
const props = defineProps({ const props = defineProps({
sidebar: Boolean sidebar: Boolean
}); });
const showModal = ref(Modal.showModal); const showModal = ref(false);
const modelModal = ref(Modal.modelModal);
</script> </script>
@ -68,7 +62,7 @@ const modelModal = ref(Modal.modelModal);
<Item v-for="notification in notifier.notifications" <Item v-for="notification in notifier.notifications"
:key="notification.id" :key="notification.id"
:notification="notification" :notification="notification"
@openModal="Modal.switchShowModal(notification)" @openModal="showModal.open(notification)"
/> />
</ul> </ul>
</div> </div>
@ -83,10 +77,8 @@ const modelModal = ref(Modal.modelModal);
</div> </div>
</section> </section>
<ShowView <ShowModal
:show="showModal" ref="showModal"
:model="modelModal"
@close="Modal.switchShowModal"
@reload="notifier.getUpdates()" @reload="notifier.getUpdates()"
/> />
</div> </div>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { computed, nextTick, onUpdated } from 'vue'; import { ref } from 'vue';
import { getDateTime } from '@Controllers/DateController'; import { getDateTime } from '@Controllers/DateController';
import useNotifier from '@Stores/Notifier'; import useNotifier from '@Stores/Notifier';
@ -17,28 +17,34 @@ const emit = defineEmits([
]); ]);
/** Propiedades */ /** Propiedades */
const props = defineProps({ const modalRef = ref(null);
show: Boolean, const model = ref({});
model: Object
});
onUpdated(() => { /** Métodos */
if(!props.model.read_at && props.show) { function readNotification() {
notifier.readNotification(props.model.id); if(!model.value.read_at) {
notifier.readNotification(model.value.id);
}
} }
if(!props.model.read_at && !props.show) { /** Exposiciones */
emit('reload'); defineExpose({
open: (data) => {
model.value = data;
modalRef.value.open();
readNotification();
} }
}); });
</script> </script>
<template> <template>
<ShowModal <ShowModal
:show="show" ref="modalRef"
@close="$emit('close')" :title="$t('notification')"
@close="$emit('reload')"
> >
<Header <Header
:title="model.data.title" :title="model.data?.title"
> >
</Header> </Header>
<div class="py-2 border-b"> <div class="py-2 border-b">
@ -53,11 +59,11 @@ onUpdated(() => {
</p> </p>
<div class="flex flex-col"> <div class="flex flex-col">
<b>{{ $t('description') }}: </b> <b>{{ $t('description') }}: </b>
{{ model.data.description }} {{ model.data?.description }}
</div> </div>
<div v-if="model.data.message" class="flex flex-col"> <div v-if="model.data?.message" class="flex flex-col">
<b>{{ $t('message') }}: </b> <b>{{ $t('message') }}: </b>
{{ model.data.message }} {{ model.data?.message }}
</div> </div>
<p> <p>
<b>{{ $t('created_at') }}: </b> <b>{{ $t('created_at') }}: </b>

View File

@ -0,0 +1,19 @@
/** Breadcrumb */
const breadcrumbItem = (name, icon, route = {name:'index'}) => ({ name, icon, route })
const breadcrumbMeta = (route) => {
let breadcrumbs = [];
route.matched.forEach(match => {
if(match.name && match.meta?.title) {
breadcrumbs.push(breadcrumbItem(match.meta.title, match.meta.icon, {name: match.name}));
}
});
return breadcrumbs;
}
export {
breadcrumbItem,
breadcrumbMeta,
}

View File

@ -1,92 +0,0 @@
import { ref } from 'vue';
/**
* Controlador simple de las bandejas
*/
class ModalController
{
// Modals
confirmModal = ref(false);
destroyModal = ref(false);
editModal = ref(false);
noteModal = ref(false);
manyNotesModal = ref(false);
showModal = ref(false);
importModal = ref(false);
// Models
modelModal = ref({});
constructor() {}
/**
* Controla el cambio entre show y edit
*/
switchShowEditModal = () => {
this.showModal.value = !this.showModal.value
this.editModal.value = !this.editModal.value
};
/**
* Controla el switch de eliminar
*/
switchShowModal = (model) => {
this._setModel(model);
this.showModal.value = !this.showModal.value
};
/**
* Controla el switch de importar
*/
switchImportModal = () => {
this.importModal.value = !this.importModal.value
};
/**
* Controla el switch de eliminar
*/
switchEditModal = (model) => {
this._setModel(model);
this.editModal.value = !this.editModal.value
};
/**
* Controla el switch de eliminar
*/
switchDestroyModal = (model) => {
this._setModel(model);
this.destroyModal.value = !this.destroyModal.value
};
/**
* Controla el switch de nota
*/
switchNoteModal = () => {
this.noteModal.value = !this.noteModal.value
};
/**
* Controla el switch de notas aplicadas a muchos
*/
switchManyNotesModal = () => {
this.manyNotesModal.value = !this.manyNotesModal.value
};
/**
* Controla el switch de nota
*/
switchConfirmModal = () => {
this.confirmModal.value = !this.confirmModal.value
};
/**
* Guarda el modelo
*/
_setModel = (model) => {
if(model) {
this.modelModal.value = model;
}
}
}
export default ModalController;

View File

@ -5,8 +5,6 @@ import { hasPermission } from '@Plugins/RolePermission';
import { useSearcher } from '@Services/Api'; import { useSearcher } from '@Services/Api';
import { apiTo, transl } from './Module'; import { apiTo, transl } from './Module';
import ModalController from '@Controllers/ModalController.js';
import IconButton from '@Holos/Button/Icon.vue' import IconButton from '@Holos/Button/Icon.vue'
import PrimaryButton from '@Holos/Button/Primary.vue'; import PrimaryButton from '@Holos/Button/Primary.vue';
import Input from '@Holos/Form/Input.vue'; import Input from '@Holos/Form/Input.vue';
@ -19,13 +17,7 @@ import ShowView from './Modals/Event.vue';
const vroute = useRoute(); const vroute = useRoute();
const router = useRouter(); const router = useRouter();
/** Controladores */
const Modal = new ModalController();
/** Propiedades */ /** Propiedades */
const showModal = ref(Modal.showModal);
const modelModal = ref(Modal.modelModal);
const models = ref([]); const models = ref([]);
const filters = reactive({ const filters = reactive({
@ -35,6 +27,9 @@ const filters = reactive({
user: '' user: ''
}); });
/** Referencias */
const showModal = ref(null);
/** Métodos */ /** Métodos */
const searcher = useSearcher({ const searcher = useSearcher({
url: apiTo('index'), url: apiTo('index'),
@ -133,7 +128,7 @@ onMounted(() => {
<template v-for="event in items"> <template v-for="event in items">
<Item <Item
:event="event" :event="event"
@show="Modal.switchShowModal(event)" @show="showModal.open(event)"
/> />
</template> </template>
</ol> </ol>
@ -142,9 +137,7 @@ onMounted(() => {
</div> </div>
<ShowView <ShowView
:show="showModal" ref="showModal"
:model="modelModal"
@close="Modal.switchShowModal"
/> />
</div> </div>
</template> </template>

View File

@ -1,4 +1,5 @@
<script setup> <script setup>
import { ref } from 'vue';
import { getDateTime } from '@Controllers/DateController'; import { getDateTime } from '@Controllers/DateController';
import Header from '@Holos/Modal/Elements/Header.vue'; import Header from '@Holos/Modal/Elements/Header.vue';
@ -11,16 +12,32 @@ defineEmits([
]); ]);
/** Propiedades */ /** Propiedades */
defineProps({ const model = ref(null);
show: Boolean,
model: Object /** Referencias */
const modalRef = ref(null);
/** Métodos */
function close() {
model.value = null;
emit('close');
}
/** Exposiciones */
defineExpose({
open: (data) => {
model.value = data;
modalRef.value.open();
}
}); });
</script> </script>
<template> <template>
<ShowModal <ShowModal
:show="show" ref="modalRef"
@close="$emit('close')" @close="close"
> >
<div v-if="model">
<Header <Header
:title="model.event" :title="model.event"
/> />
@ -55,5 +72,6 @@ defineProps({
</div> </div>
</div> </div>
</div> </div>
</div>
</ShowModal> </ShowModal>
</template> </template>

View File

@ -3,21 +3,15 @@ import { onMounted, ref } from 'vue';
import { can, apiTo, viewTo, transl } from './Module' import { can, apiTo, viewTo, transl } from './Module'
import { useSearcher } from '@Services/Api'; import { useSearcher } from '@Services/Api';
import ModalController from '@Controllers/ModalController.js';
import IconButton from '@Holos/Button/Icon.vue' import IconButton from '@Holos/Button/Icon.vue'
import DestroyView from '@Holos/Modal/Template/Destroy.vue'; import DestroyView from '@Holos/Modal/Template/Destroy.vue';
import SearcherHead from '@Holos/Searcher.vue'; import SearcherHead from '@Holos/Searcher.vue';
import Table from '@Holos/Table.vue'; import Table from '@Holos/Table.vue';
import Permissions from './Modals/Permissions.vue'; import Permissions from './Modals/Permissions.vue';
/** Controladores */
const Modal = new ModalController();
/** Propiedades */ /** Propiedades */
const destroyModal = ref(Modal.destroyModal); const destroyModal = ref(null);
const editModal = ref(Modal.editModal); const editModal = ref(null);
const modelModal = ref(Modal.modelModal);
const models = ref([]); const models = ref([]);
@ -83,7 +77,7 @@ onMounted(() => {
v-if="can('edit') && ![1,2].includes(model.id)" v-if="can('edit') && ![1,2].includes(model.id)"
icon="license" icon="license"
:title="transl('permissions.title')" :title="transl('permissions.title')"
@click="Modal.switchEditModal(model)" @click="editModal.open(model)"
outline outline
/> />
<RouterLink <RouterLink
@ -101,7 +95,7 @@ onMounted(() => {
v-if="can('destroy') && ![1,2].includes(model.id)" v-if="can('destroy') && ![1,2].includes(model.id)"
icon="delete" icon="delete"
:title="$t('crud.destroy')" :title="$t('crud.destroy')"
@click="Modal.switchDestroyModal(model)" @click="destroyModal.open(model)"
outline outline
/> />
</div> </div>
@ -113,18 +107,14 @@ onMounted(() => {
<Permissions <Permissions
v-if="can('index')" v-if="can('index')"
:show="editModal" ref="editModal"
:model="modelModal"
@close="Modal.switchEditModal"
/> />
<DestroyView <DestroyView
v-if="can('destroy')" v-if="can('destroy')"
ref="destroyModal"
title="description" title="description"
subtitle="" subtitle=""
:model="modelModal"
:show="destroyModal"
:to="(role) => apiTo('destroy', { role })" :to="(role) => apiTo('destroy', { role })"
@close="Modal.switchDestroyModal"
@update="searcher.search()" @update="searcher.search()"
/> />
</div> </div>

View File

@ -4,8 +4,9 @@ import { api } from '@Services/Api';
import { apiTo } from '../Module'; 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/Show.vue';
import Checkbox from '@Holos/Form/Checkbox.vue'; import Checkbox from '@Holos/Form/Checkbox.vue';
import PrimaryButton from '@Holos/Button/Primary.vue';
/** Eventos */ /** Eventos */
const emit = defineEmits([ const emit = defineEmits([
@ -13,49 +14,61 @@ const emit = defineEmits([
]); ]);
/** Propiedades */ /** Propiedades */
const props = defineProps({
show: Boolean,
model: Object
});
const permissionTypes = ref([]); const permissionTypes = ref([]);
const permissions = ref([]); const permissions = ref([]);
const model = ref(null);
/** Referencias */
const modalRef = ref(null);
/** Métodos */ /** Métodos */
function close() {
modalRef.value.close();
emit('close');
}
function update() { function update() {
api.put(apiTo('permissions', { role: props.model.id }), { api.put(apiTo('permissions', { role: model.value.id }), {
data: { data: {
permissions: permissions.value permissions: permissions.value
}, },
onSuccess: () => { onSuccess: () => {
Notify.success(Lang('register.edit.onSuccess')) Notify.success(Lang('register.edit.onSuccess'))
emit('close'); close();
} }
}); });
} }
/** Ciclos */ function getPermissions() {
onUpdated(() => {
api.get(route('permission-types.all-with-permissions'), { api.get(route('permission-types.all-with-permissions'), {
onSuccess: (r) => permissionTypes.value = r.models onSuccess: (r) => permissionTypes.value = r.models
}); });
api.get(apiTo('permissions', { role: props.model.id }), { api.get(apiTo('permissions', { role: model.value.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);
} }
} }
}); });
}
/** Exposiciones */
defineExpose({
open: (data) => {
model.value = data;
getPermissions();
modalRef.value.open();
}
}); });
</script> </script>
<template> <template>
<EditModal <EditModal
:show="show" ref="modalRef"
@update="update" @close="close"
@close="$emit('close')"
> >
<div v-if="model">
<Header <Header
:title="model.description" :title="model.description"
/> />
@ -75,5 +88,13 @@ onUpdated(() => {
</div> </div>
</div> </div>
</div> </div>
</div>
<template #buttons>
<PrimaryButton
@click="update"
>
{{ $t('update') }}
</PrimaryButton>
</template>
</EditModal> </EditModal>
</template> </template>

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { api, useForm } from '@Services/Api'; import { api, useForm } from '@Services/Api';
import { apiTo, transl, viewTo } from './Module'; import { apiTo, transl, viewTo } from './Module';
@ -50,7 +50,9 @@ onMounted(() => {
</script> </script>
<template> <template>
<PageHeader :title="transl('create.title')"> <PageHeader
:title="transl('create.title')"
>
<RouterLink :to="viewTo({ name: 'index' })"> <RouterLink :to="viewTo({ name: 'index' })">
<IconButton <IconButton
class="text-white" class="text-white"

View File

@ -1,10 +1,8 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { can, apiTo, viewTo, transl } from './Module'
import { useSearcher } from '@Services/Api'; import { useSearcher } from '@Services/Api';
import { hasPermission } from '@Plugins/RolePermission'; import { hasPermission } from '@Plugins/RolePermission';
import { can, apiTo, viewTo, transl } from './Module'
import ModalController from '@Controllers/ModalController.js';
import IconButton from '@Holos/Button/Icon.vue' import IconButton from '@Holos/Button/Icon.vue'
import DestroyView from '@Holos/Modal/Template/Destroy.vue'; import DestroyView from '@Holos/Modal/Template/Destroy.vue';
@ -12,16 +10,13 @@ import SearcherHead from '@Holos/Searcher.vue';
import Table from '@Holos/Table.vue'; import Table from '@Holos/Table.vue';
import ShowView from './Modals/Show.vue'; import ShowView from './Modals/Show.vue';
/** Controladores */
const Modal = new ModalController();
/** Propiedades */ /** Propiedades */
const destroyModal = ref(Modal.destroyModal);
const showModal = ref(Modal.showModal);
const modelModal = ref(Modal.modelModal);
const models = ref([]); const models = ref([]);
/** Referencias */
const showModal = ref(false);
const destroyModal = ref(false);
/** Métodos */ /** Métodos */
const searcher = useSearcher({ const searcher = useSearcher({
url: apiTo('index'), url: apiTo('index'),
@ -106,7 +101,7 @@ onMounted(() => {
<IconButton <IconButton
icon="visibility" icon="visibility"
:title="$t('crud.show')" :title="$t('crud.show')"
@click="Modal.switchShowModal(model)" @click="showModal.open(model)"
outline outline
/> />
<RouterLink <RouterLink
@ -124,7 +119,7 @@ onMounted(() => {
v-if="can('destroy')" v-if="can('destroy')"
icon="delete" icon="delete"
:title="$t('crud.destroy')" :title="$t('crud.destroy')"
@click="Modal.switchDestroyModal(model)" @click="destroyModal.open(model)"
outline outline
/> />
<RouterLink <RouterLink
@ -166,18 +161,14 @@ onMounted(() => {
</div> </div>
<ShowView <ShowView
v-if="can('index')" ref="showModal"
:show="showModal"
:model="modelModal"
@close="Modal.switchShowModal"
/> />
<DestroyView <DestroyView
v-if="can('destroy')" v-if="can('destroy')"
ref="destroyModal"
subtitle="last_name" subtitle="last_name"
:model="modelModal"
:show="destroyModal"
:to="(user) => apiTo('destroy', { user })" :to="(user) => apiTo('destroy', { user })"
@close="Modal.switchDestroyModal"
@update="searcher.search()" @update="searcher.search()"
/> />
</div> </div>

View File

@ -1,25 +1,45 @@
<script setup> <script setup>
import { ref } from 'vue';
import { getDateTime } from '@Controllers/DateController'; import { getDateTime } from '@Controllers/DateController';
import Header from '@Holos/Modal/Elements/Header.vue'; import Header from '@Holos/Modal/Elements/Header.vue';
import ShowModal from '@Holos/Modal/Show.vue'; import ShowModal from '@Holos/Modal/Show.vue';
import GoogleIcon from '@Shared/GoogleIcon.vue'; import GoogleIcon from '@Shared/GoogleIcon.vue';
/** Eventos */ /** Eventos */
defineEmits([ const emit = defineEmits([
'close', 'close',
'reload'
]); ]);
/** Propiedades */ /** Propiedades */
defineProps({ const model = ref(null);
show: Boolean,
model: Object /** Referencias */
const modalRef = ref(null);
/** Métodos */
function close() {
model.value = null;
emit('close');
}
/** Exposiciones */
defineExpose({
open: (data) => {
model.value = data;
modalRef.value.open();
}
}); });
</script> </script>
<template> <template>
<ShowModal <ShowModal
:show="show" ref="modalRef"
@close="$emit('close')" @close="close"
> >
<div v-if="model">
<Header <Header
:title="model.name" :title="model.name"
:subtitle="model.last_name" :subtitle="model.last_name"
@ -65,5 +85,6 @@ defineProps({
</p> </p>
</div> </div>
</div> </div>
</div>
</ShowModal> </ShowModal>
</template> </template>

View File

@ -3,21 +3,15 @@ import { ref } from 'vue';
import { can, apiTo, viewTo, transl } from './Module' import { can, apiTo, viewTo, transl } from './Module'
import { users } from '@Plugins/AuthUsers' import { users } from '@Plugins/AuthUsers'
import ModalController from '@Controllers/ModalController.js';
import IconButton from '@Holos/Button/Icon.vue' import IconButton from '@Holos/Button/Icon.vue'
import DestroyView from '@Holos/Modal/Template/Destroy.vue'; import DestroyView from '@Holos/Modal/Template/Destroy.vue';
import Header from '@Holos/PageHeader.vue'; import Header from '@Holos/PageHeader.vue';
import Table from '@Holos/TableSimple.vue'; import Table from '@Holos/TableSimple.vue';
import ShowView from './Modals/Show.vue'; import ShowView from './Modals/Show.vue';
/** Controladores */
const Modal = new ModalController();
/** Propiedades */ /** Propiedades */
const destroyModal = ref(Modal.destroyModal); const destroyModal = ref(null);
const showModal = ref(Modal.showModal); const showModal = ref(null);
const modelModal = ref(Modal.modelModal);
</script> </script>
@ -87,7 +81,7 @@ const modelModal = ref(Modal.modelModal);
<IconButton <IconButton
icon="visibility" icon="visibility"
:title="$t('crud.show')" :title="$t('crud.show')"
@click="Modal.switchShowModal(model)" @click="showModal.open(model)"
outline outline
/> />
<RouterLink <RouterLink
@ -105,7 +99,7 @@ const modelModal = ref(Modal.modelModal);
v-if="can('destroy')" v-if="can('destroy')"
icon="delete" icon="delete"
:title="$t('crud.destroy')" :title="$t('crud.destroy')"
@click="Modal.switchDestroyModal(model)" @click="destroyModal.open(model)"
outline outline
/> />
<RouterLink <RouterLink
@ -127,17 +121,14 @@ const modelModal = ref(Modal.modelModal);
</div> </div>
<ShowView <ShowView
v-if="can('index')" ref="showModal"
:show="showModal"
:model="modelModal"
@close="Modal.switchShowModal"
/> />
<DestroyView <DestroyView
v-if="can('destroy')" v-if="can('destroy')"
:model="modelModal" ref="destroyModal"
:show="destroyModal" subtitle="last_name"
:to="(user) => apiTo('destroy', { user })" :to="(user) => apiTo('destroy', { user })"
@close="Modal.switchDestroyModal"
@update="searcher.search()" @update="searcher.search()"
/> />
</div> </div>

View File

@ -100,6 +100,24 @@ const changelogs = [
'UPDATE: Actualización de dependencias.' 'UPDATE: Actualización de dependencias.'
], ],
date: '2025-05-23' date: '2025-05-23'
},
{
version: '0.9.11',
details: [
'FIX: El useApi clonaba comportamientos en instancias de la misma página.',
'UPDATE: Actualización de dependencias.'
],
date: '2025-05-23'
},
{
version: '0.9.12',
details: [
'FIX: Token de sesión ahora se almacena en localStorage para mantener la sesión activa en el frontend por el tiempo de vida del token.',
'UPDATE: Actualización de dependencias.',
'UPDATE: Funcionamiento de los modals.',
'ADD: Breadcrumbs.'
],
date: '2025-07-14'
} }
] ]
</script> </script>

View File

@ -2,25 +2,21 @@
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useSearcher } from '@Services/Api'; import { useSearcher } from '@Services/Api';
import ModalController from '@Controllers/ModalController.js';
import { getDateTime } from '@Controllers/DateController.js'; import { getDateTime } from '@Controllers/DateController.js';
import SearcherHead from '@Holos/Searcher.vue'; import SearcherHead from '@Holos/Searcher.vue';
import Table from '@Holos/Table.vue'; import Table from '@Holos/Table.vue';
import IconButton from '@Holos/Button/Icon.vue'; import IconButton from '@Holos/Button/Icon.vue';
import ShowView from '@Holos/Skeleton/Sidebar/Notification/Show.vue'; import DestroyModal from '@Holos/Modal/Template/Destroy.vue';
import GoogleIcon from '@Shared/GoogleIcon.vue'; import ShowModal from '@Holos/Skeleton/Sidebar/Notification/Show.vue';
/** Controladores */
const Modal = new ModalController();
/** Propiedades */ /** Propiedades */
// const destroyModal = ref(Modal.destroyModal); const destroyModal = ref(null);
const showModal = ref(Modal.showModal); const showModal = ref(false);
const modelModal = ref(Modal.modelModal);
const models = ref([]); const models = ref([]);
// Servicios
const searcher = useSearcher({ const searcher = useSearcher({
url: route('system.notifications.all'), url: route('system.notifications.all'),
onSuccess: (r) => models.value = r.models, onSuccess: (r) => models.value = r.models,
@ -89,17 +85,15 @@ onMounted(() => {
<IconButton <IconButton
icon="visibility" icon="visibility"
:title="$t('crud.show')" :title="$t('crud.show')"
@click="Modal.switchShowModal(model)" @click="showModal.open(model)"
outline outline
/> />
<!-- <GoogleIcon <IconButton
v-if="can('destroy')" icon="delete"
class="btn-icon"
name="delete"
:title="$t('crud.destroy')" :title="$t('crud.destroy')"
@click="Modal.switchDestroyModal(model)" @click="destroyModal.open(model)"
outline outline
/> --> />
</div> </div>
</td> </td>
</tr> </tr>
@ -120,20 +114,15 @@ onMounted(() => {
</Table> </Table>
</div> </div>
<ShowView <ShowModal
:show="showModal" ref="showModal"
:model="modelModal"
@close="Modal.switchShowModal"
@reload="searcher.search()" @reload="searcher.search()"
/> />
<!-- <DestroyView <DestroyModal
v-if="can('destroy')" ref="destroyModal"
:model="modelModal" :to="(id) => route('system.notifications.destroy', { id })"
:show="destroyModal" @update="searcher.search()"
:to="(user) => apiTo('destroy', { user })" />
@close="Modal.switchDestroyModal"
@update="getNotifications"
/> -->
</div> </div>
</template> </template>

View File

@ -5,11 +5,11 @@ import { logout } from '@Services/Page';
import ActionSection from '@Holos/ActionSection.vue'; import ActionSection from '@Holos/ActionSection.vue';
import DangerButton from '@Holos/Button/Danger.vue'; import DangerButton from '@Holos/Button/Danger.vue';
import DialogModal from '@Holos/DialogModal.vue'; import DialogModal from '@Holos/Modal/Elements/Base.vue';
import SecondaryButton from '@Holos/Button/Secondary.vue'; import SecondaryButton from '@Holos/Button/Secondary.vue';
import Input from '@Holos/Form/Input.vue'; import Input from '@Holos/Form/Input.vue';
const confirmingUserDeletion = ref(false); const modalRef = ref(null);
const passwordInput = ref(null); const passwordInput = ref(null);
const form = useForm({ const form = useForm({
@ -17,7 +17,7 @@ const form = useForm({
}); });
const confirmUserDeletion = () => { const confirmUserDeletion = () => {
confirmingUserDeletion.value = true; modalRef.value.open();
setTimeout(() => passwordInput.value.focus(), 250); setTimeout(() => passwordInput.value.focus(), 250);
}; };
@ -26,7 +26,7 @@ const deleteUser = () => {
form.delete(route('user.destroy'), { form.delete(route('user.destroy'), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => { onSuccess: () => {
closeModal(); modalRef.value.close();
logout(); logout();
}, },
onError: () => passwordInput.value.focus(), onError: () => passwordInput.value.focus(),
@ -34,9 +34,7 @@ const deleteUser = () => {
}); });
}; };
const closeModal = () => { const onCloseModal = () => {
confirmingUserDeletion.value = false;
form.reset(); form.reset();
}; };
</script> </script>
@ -63,7 +61,10 @@ const closeModal = () => {
</div> </div>
<!-- Delete Account Confirmation Modal --> <!-- Delete Account Confirmation Modal -->
<DialogModal :show="confirmingUserDeletion" @close="closeModal"> <DialogModal
ref="modalRef"
@close="onCloseModal"
>
<template #title> <template #title>
{{ $t('account.delete.title') }} {{ $t('account.delete.title') }}
</template> </template>
@ -86,7 +87,7 @@ const closeModal = () => {
</template> </template>
<template #footer> <template #footer>
<SecondaryButton @click="closeModal"> <SecondaryButton @click="modalRef.close()">
{{ $t('cancel') }} {{ $t('cancel') }}
</SecondaryButton> </SecondaryButton>

View File

@ -3,7 +3,7 @@ import { ref } from 'vue';
import { useForm } from '@Services/Api'; import { useForm } from '@Services/Api';
import ActionSection from '@Holos/ActionSection.vue'; import ActionSection from '@Holos/ActionSection.vue';
import DialogModal from '@Holos/DialogModal.vue'; import DialogModal from '@Holos/Modal/Elements/Base.vue';
import PrimaryButton from '@Holos/Button/Primary.vue'; import PrimaryButton from '@Holos/Button/Primary.vue';
import SecondaryButton from '@Holos/Button/Secondary.vue'; import SecondaryButton from '@Holos/Button/Secondary.vue';
import Input from '@Holos/Form/Input.vue'; import Input from '@Holos/Form/Input.vue';

View File

@ -17,16 +17,26 @@ const router = createRouter({
{ {
path: '/', path: '/',
component: () => import('@Layouts/AppLayout.vue'), component: () => import('@Layouts/AppLayout.vue'),
name: 'root',
meta: {
title: 'Inicio',
icon: 'home',
},
redirect: '/dashboard',
children: [ children: [
{ {
path: '', path: '',
name: 'index', name: 'index',
redirect: '/dashboard' redirect: '/dashboard',
meta: {
title: 'Inicio',
icon: 'home',
},
}, },
{ {
path: 'dashboard', path: 'dashboard',
name: 'dashboard.index', name: 'dashboard.index',
component: () => import('@Pages/Dashboard/Index.vue') component: () => import('@Pages/Dashboard/Index.vue'),
}, },
{ {
path: 'profile', path: 'profile',
@ -34,7 +44,11 @@ const router = createRouter({
{ {
path: '', path: '',
name: 'profile.show', name: 'profile.show',
component: () => import('@Pages/Profile/Show.vue') component: () => import('@Pages/Profile/Show.vue'),
meta: {
title: 'Perfil',
icon: 'person',
},
}, },
{ {
path: 'notifications', path: 'notifications',
@ -52,16 +66,28 @@ const router = createRouter({
}, },
{ {
path: '/admin', path: '/admin',
name: 'admin',
component: () => import('@Layouts/AppLayout.vue'), component: () => import('@Layouts/AppLayout.vue'),
meta: {
title: 'Inicio',
icon: 'home',
},
redirect: '/',
children: [ children: [
{ {
path: 'users', path: 'users',
name: 'admin.users',
meta: {
title: 'Usuarios',
icon: 'people',
},
redirect: '/admin/users',
children: [ children: [
{ {
path: '', path: '',
name: 'admin.users.index', name: 'admin.users.index',
beforeEnter: (to, from, next) => can(next, 'users.index'), beforeEnter: (to, from, next) => can(next, 'users.index'),
component: () => import('@Pages/Admin/Users/Index.vue') component: () => import('@Pages/Admin/Users/Index.vue'),
}, },
{ {
path: 'online', path: 'online',
@ -73,41 +99,73 @@ const router = createRouter({
path: 'create', path: 'create',
name: 'admin.users.create', name: 'admin.users.create',
beforeEnter: (to, from, next) => can(next, 'users.create'), beforeEnter: (to, from, next) => can(next, 'users.create'),
meta: {
title: 'Crear',
icon: 'add',
},
component: () => import('@Pages/Admin/Users/Create.vue') component: () => import('@Pages/Admin/Users/Create.vue')
}, { }, {
path: ':id/edit', path: ':id/edit',
name: 'admin.users.edit', name: 'admin.users.edit',
beforeEnter: (to, from, next) => can(next, 'users.edit'), beforeEnter: (to, from, next) => can(next, 'users.edit'),
meta: {
title: 'Editar',
icon: 'edit',
},
component: () => import('@Pages/Admin/Users/Edit.vue') component: () => import('@Pages/Admin/Users/Edit.vue')
}, { }, {
path: ':id/settings', path: ':id/settings',
name: 'admin.users.settings', name: 'admin.users.settings',
beforeEnter: (to, from, next) => can(next, 'users.settings'), beforeEnter: (to, from, next) => can(next, 'users.settings'),
component: () => import('@Pages/Admin/Users/Settings.vue') component: () => import('@Pages/Admin/Users/Settings.vue'),
meta: {
title: 'Configuración',
icon: 'settings',
},
} }
] ]
}, },
{ {
path: 'roles', path: 'roles',
name: 'admin.roles',
meta: {
title: 'Roles',
icon: 'license',
},
redirect: '/admin/roles',
children: [ children: [
{ {
path: '', path: '',
name: 'admin.roles.index', name: 'admin.roles.index',
component: () => import('@Pages/Admin/Roles/Index.vue') component: () => import('@Pages/Admin/Roles/Index.vue'),
}, },
{ {
path: 'create', path: 'create',
name: 'admin.roles.create', name: 'admin.roles.create',
component: () => import('@Pages/Admin/Roles/Create.vue') component: () => import('@Pages/Admin/Roles/Create.vue'),
meta: {
title: 'Crear',
icon: 'add',
},
}, { }, {
path: ':id/edit', path: ':id/edit',
name: 'admin.roles.edit', name: 'admin.roles.edit',
component: () => import('@Pages/Admin/Roles/Edit.vue') component: () => import('@Pages/Admin/Roles/Edit.vue'),
meta: {
title: 'Editar',
icon: 'edit',
},
} }
] ]
}, },
{ {
path: 'activities', path: 'activities',
name: 'admin.activities',
meta: {
title: 'Historial de acciones',
icon: 'event',
},
redirect: '/admin/activities',
children: [ children: [
{ {
path: '', path: '',

View File

@ -23,7 +23,7 @@ const failCodes = [
/** /**
* Servidor a utilizar * Servidor a utilizar
*/ */
const token = ref(sessionStorage.token); const token = ref(localStorage.token);
const csrfToken = ref(localStorage.csrfToken); const csrfToken = ref(localStorage.csrfToken);
/** /**
@ -31,7 +31,7 @@ const csrfToken = ref(localStorage.csrfToken);
*/ */
const defineApiToken = (x) => { const defineApiToken = (x) => {
token.value = x; token.value = x;
sessionStorage.token = x; localStorage.token = x;
} }
/** /**
@ -47,7 +47,7 @@ const defineCsrfToken = (x) => {
*/ */
const resetApiToken = () => { const resetApiToken = () => {
token.value = undefined; token.value = undefined;
sessionStorage.removeItem('token'); localStorage.removeItem('token');
} }
/** /**
@ -250,7 +250,7 @@ const api = {
/** /**
* Instancia de la API * Instancia de la API
*/ */
const useApi = () => reactive(api); const useApi = () => reactive({...api});
/** /**
* Instancia de la API para formularios * Instancia de la API para formularios