ADD: Plantilla Holos (#1)
This commit is contained in:
parent
97b85a1f65
commit
b6c8a347cd
11
.env.example
11
.env.example
@ -1 +1,10 @@
|
||||
VITE_API_URL=http://backend.holos.test
|
||||
VITE_API_URL=http://backend.holos.test
|
||||
VITE_BASE_URL=http://frontend.holos.test
|
||||
|
||||
VITE_REVERB_APP_ID=
|
||||
VITE_REVERB_APP_KEY=
|
||||
VITE_REVERB_APP_SECRET=
|
||||
VITE_REVERB_HOST="backend.holos.test"
|
||||
VITE_REVERB_PORT=8080
|
||||
VITE_REVERB_SCHEME=http
|
||||
VITE_REVERB_ACTIVE=false
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,6 +12,8 @@ dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.env
|
||||
colors.json
|
||||
notes.md
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
|
||||
14
auth.html
Normal file
14
auth.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html id="main-page" lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Login</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/auth.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
34
colors.json.example
Normal file
34
colors.json.example
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"page":"#fff",
|
||||
"page-t":"#000",
|
||||
"page-d":"#292524",
|
||||
"page-dt":"#fff",
|
||||
"primary":"#374151",
|
||||
"primary-t":"#fff",
|
||||
"primary-d":"#1c1917",
|
||||
"primary-dt":"#fff",
|
||||
"secondary":"#3b82f6",
|
||||
"secondary-t":"#fff",
|
||||
"secondary-d":"#312e81",
|
||||
"secondary-dt":"#fff",
|
||||
"primary-info":"#06b6d4",
|
||||
"primary-info-t":"#fff",
|
||||
"primary-info-d":"#06b6d4",
|
||||
"primary-info-dt":"#fff",
|
||||
"secondary-info":"#06b6d4",
|
||||
"secondary-info-t":"#fff",
|
||||
"secondary-info-d":"#06b6d4",
|
||||
"secondary-info-dt":"#fff",
|
||||
"success":"#22c55e",
|
||||
"success-t":"#fff",
|
||||
"success-d":"#22c55e",
|
||||
"success-dt":"#fff",
|
||||
"danger":"#ef4444",
|
||||
"danger-t":"#fff",
|
||||
"danger-d":"#ef4444",
|
||||
"danger-dt":"#fecaca",
|
||||
"warning":"#eab308",
|
||||
"warning-t":"#fff",
|
||||
"warning-d":"#eab308",
|
||||
"warning-dt":"#fff"
|
||||
}
|
||||
@ -4,11 +4,11 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue</title>
|
||||
<title>Holos</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
<script type="module" src="/src/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
69
package-lock.json
generated
69
package-lock.json
generated
@ -1,22 +1,27 @@
|
||||
{
|
||||
"name": "holos.frontend",
|
||||
"version": "0.0.0",
|
||||
"name": "notsoweb.frontend",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "holos.frontend",
|
||||
"version": "0.0.0",
|
||||
"name": "notsoweb.frontend",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"axios": "^1.7.8",
|
||||
"laravel-echo": "^1.17.1",
|
||||
"luxon": "^3.5.0",
|
||||
"pinia": "^2.2.8",
|
||||
"postcss": "^8.4.49",
|
||||
"pusher-js": "^8.4.0-rc2",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"toastr": "^2.1.4",
|
||||
"uuid": "^11.0.3",
|
||||
"vite": "^6.0.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^10.0.5",
|
||||
"vue-multiselect": "^3.1.0",
|
||||
"vue-router": "^4.5.0",
|
||||
"ziggy-js": "^2.4.1"
|
||||
},
|
||||
@ -1687,6 +1692,15 @@
|
||||
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/laravel-echo": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.17.1.tgz",
|
||||
"integrity": "sha512-ORWc4vDfnBj/Oe5ThZ5kYyGItRjLDqAQUyhD/7UhehUOqc+s5x9HEBjtMVludNMP6VuXw6t7Uxt8bp63kaTofg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
||||
@ -1708,6 +1722,15 @@
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
|
||||
"integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.14",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz",
|
||||
@ -2143,6 +2166,15 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pusher-js": {
|
||||
"version": "8.4.0-rc2",
|
||||
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0-rc2.tgz",
|
||||
"integrity": "sha512-d87GjOEEl9QgO5BWmViSqW0LOzPvybvX6WA9zLUstNdB57jVJuR27zHkRnrav2a3+zAMlHbP2Og8wug+rG8T+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tweetnacl": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.9.7",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
|
||||
@ -2539,6 +2571,12 @@
|
||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
|
||||
@ -2576,6 +2614,19 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz",
|
||||
"integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.1.tgz",
|
||||
@ -2688,6 +2739,16 @@
|
||||
"vue": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-multiselect": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-multiselect/-/vue-multiselect-3.1.0.tgz",
|
||||
"integrity": "sha512-+i/fjTqFBpaay9NP+lU7obBeNaw2DdFDFs4mqhsM0aEtKRdvIf7CfREAx2o2B4XDmPrBt1r7x1YCM3BOMLaUgQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.1",
|
||||
"npm": ">= 6.14.15"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz",
|
||||
|
||||
10
package.json
10
package.json
@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "holos.frontend",
|
||||
"name": "notsoweb.frontend",
|
||||
"copyright": "Notsoweb Software Inc.",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@ -11,13 +12,18 @@
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"axios": "^1.7.8",
|
||||
"laravel-echo": "^1.17.1",
|
||||
"luxon": "^3.5.0",
|
||||
"pinia": "^2.2.8",
|
||||
"postcss": "^8.4.49",
|
||||
"pusher-js": "^8.4.0-rc2",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"toastr": "^2.1.4",
|
||||
"uuid": "^11.0.3",
|
||||
"vite": "^6.0.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^10.0.5",
|
||||
"vue-multiselect": "^3.1.0",
|
||||
"vue-router": "^4.5.0",
|
||||
"ziggy-js": "^2.4.1"
|
||||
},
|
||||
|
||||
2
public/images/.gitignore
vendored
Normal file
2
public/images/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
12
src/App.vue
12
src/App.vue
@ -1,12 +0,0 @@
|
||||
<script setup>
|
||||
|
||||
console.log(route('api.routes'));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1 class="text-3xl font-bold underline">
|
||||
Hello world!
|
||||
</h1>
|
||||
|
||||
{{ route('api.routes') }}
|
||||
</template>
|
||||
@ -5,14 +5,15 @@ import { createPinia } from 'pinia'
|
||||
import { createApp } from 'vue'
|
||||
import { useRoute, ZiggyVue } from 'ziggy-js';
|
||||
import { i18n, lang } from '@/lang/i18n.js';
|
||||
import router from '@Router/Auth'
|
||||
import Notify from '@Plugins/Notify'
|
||||
import TailwindScreen from '@Plugins/TailwindScreen'
|
||||
import { pagePlugin } from '@Services/Page';
|
||||
|
||||
import App from './App.vue'
|
||||
import Auth from '@Holos/Layout/Auth.vue'
|
||||
|
||||
// Configurar axios
|
||||
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
axios.defaults.baseURL = import.meta.env.VITE_API_URL;
|
||||
|
||||
// Crear instancias globales
|
||||
window.Lang = lang;
|
||||
@ -21,7 +22,7 @@ window.TwScreen = new TailwindScreen();
|
||||
|
||||
async function boot() {
|
||||
try {
|
||||
const { data } = await axios.get('/api/routes');
|
||||
const { data } = await axios.get(import.meta.env.VITE_API_URL + '/api/routes');
|
||||
|
||||
// Iniciar rutas
|
||||
window.Ziggy = data;
|
||||
@ -31,9 +32,11 @@ async function boot() {
|
||||
alert('Failed to load routes');
|
||||
}
|
||||
|
||||
createApp(App)
|
||||
createApp(Auth)
|
||||
.use(createPinia())
|
||||
.use(i18n)
|
||||
.use(pagePlugin)
|
||||
.use(router)
|
||||
.use(ZiggyVue)
|
||||
.mount('#app');
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
msg: String,
|
||||
})
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100">
|
||||
<div>
|
||||
<slot name="logo" />
|
||||
</div>
|
||||
|
||||
<div class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,17 +0,0 @@
|
||||
<script setup>
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Link :href="'/'">
|
||||
<svg
|
||||
class="w-16 h-16"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M11.395 44.428C4.557 40.198 0 32.632 0 24 0 10.745 10.745 0 24 0a23.891 23.891 0 0113.997 4.502c-.2 17.907-11.097 33.245-26.602 39.926z" fill="#6875F5" />
|
||||
<path d="M14.134 45.885A23.914 23.914 0 0024 48c13.255 0 24-10.745 24-24 0-3.516-.756-6.856-2.115-9.866-4.659 15.143-16.608 27.092-31.75 31.751z" fill="#6875F5" />
|
||||
</svg>
|
||||
</Link>
|
||||
</template>
|
||||
@ -5,11 +5,11 @@ import GoogleIcon from '@Shared/GoogleIcon.vue'
|
||||
const props = defineProps({
|
||||
icon: String,
|
||||
fill: Boolean,
|
||||
title: String,
|
||||
style: {
|
||||
type: String,
|
||||
default: 'rounded'
|
||||
},
|
||||
title: String,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'button'
|
||||
@ -24,8 +24,8 @@ const props = defineProps({
|
||||
:type="type"
|
||||
>
|
||||
<GoogleIcon
|
||||
:name="icon"
|
||||
:fill="fill"
|
||||
:name="icon"
|
||||
:style="style"
|
||||
/>
|
||||
</button>
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
<script setup>
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { RouterLink } from 'vue-router';
|
||||
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
|
||||
/** Propiedades */
|
||||
defineProps({
|
||||
to: String,
|
||||
icon: String,
|
||||
title: String,
|
||||
value: Number,
|
||||
icon: String
|
||||
to: String,
|
||||
value: Number
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Link
|
||||
<RouterLink
|
||||
class="relative flex-1 flex flex-col gap-2 p-4 rounded -md bg-gray-200 dark:bg-transparent dark:border"
|
||||
:href="to"
|
||||
:to="to"
|
||||
>
|
||||
<label class="text-base font-semibold tracking-wider">
|
||||
{{ title }}
|
||||
@ -30,5 +30,5 @@ defineProps({
|
||||
filled
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</template>
|
||||
@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick } from 'vue';
|
||||
import { ref, nextTick } from 'vue';
|
||||
import { api, useForm } from '@Services/Api';
|
||||
|
||||
import Input from './Form/Input.vue';
|
||||
import DialogModal from './DialogModal.vue';
|
||||
@ -11,62 +12,45 @@ const emit = defineEmits(['confirmed']);
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: lang('confirm'),
|
||||
default: Lang('confirm'),
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: lang('account.password.verify'),
|
||||
default: Lang('account.password.verify'),
|
||||
},
|
||||
button: {
|
||||
type: String,
|
||||
default: lang('confirm'),
|
||||
default: Lang('confirm'),
|
||||
},
|
||||
});
|
||||
|
||||
const confirmingPassword = ref(false);
|
||||
|
||||
const form = reactive({
|
||||
const form = useForm({
|
||||
password: '',
|
||||
error: '',
|
||||
processing: false,
|
||||
});
|
||||
|
||||
const passwordInput = ref(null);
|
||||
|
||||
const startConfirmingPassword = () => {
|
||||
axios.get(route('password.confirmation')).then(response => {
|
||||
if (response.data.confirmed) {
|
||||
emit('confirmed');
|
||||
} else {
|
||||
confirmingPassword.value = true;
|
||||
|
||||
setTimeout(() => passwordInput.value.focus(), 250);
|
||||
}
|
||||
});
|
||||
confirmingPassword.value = true;
|
||||
};
|
||||
|
||||
const confirmPassword = () => {
|
||||
form.processing = true;
|
||||
|
||||
axios.post(route('password.confirm'), {
|
||||
password: form.password,
|
||||
}).then(() => {
|
||||
form.processing = false;
|
||||
|
||||
closeModal();
|
||||
nextTick().then(() => emit('confirmed'));
|
||||
|
||||
}).catch(error => {
|
||||
form.processing = false;
|
||||
form.error = error.response.data.errors.password[0];
|
||||
passwordInput.value.focus();
|
||||
form.post(route('user.password-confirm'), {
|
||||
onSuccess: () => {
|
||||
closeModal();
|
||||
nextTick(() => emit('confirmed'));
|
||||
},
|
||||
onFail: () => {
|
||||
passwordInput.value.focus();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
confirmingPassword.value = false;
|
||||
form.password = '';
|
||||
form.error = '';
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -84,12 +68,14 @@ const closeModal = () => {
|
||||
<template #content>
|
||||
{{ content }}
|
||||
|
||||
{{ form }}
|
||||
|
||||
<div class="mt-4">
|
||||
<Input
|
||||
v-model="form.password"
|
||||
id="password"
|
||||
type="password"
|
||||
:onError="form.error"
|
||||
:onError="form.errors.password"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<script setup>
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { RouterLink } from 'vue-router'
|
||||
|
||||
defineProps({
|
||||
as: String,
|
||||
href: String
|
||||
to: String
|
||||
});
|
||||
|
||||
const style = 'block px-4 py-2 text-sm leading-5 hover:bg-secondary/80 dark:hover:bg-secondary-d/80 focus:outline-none focus:bg-gray-100 transition';
|
||||
@ -28,12 +28,12 @@ const style = 'block px-4 py-2 text-sm leading-5 hover:bg-secondary/80 dark:hove
|
||||
<slot />
|
||||
</a>
|
||||
|
||||
<Link
|
||||
<RouterLink
|
||||
v-else
|
||||
:href="href"
|
||||
:to="$view({ name: to })"
|
||||
:class="style"
|
||||
>
|
||||
<slot />
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
/** Propiedades */
|
||||
defineProps({
|
||||
onError: String
|
||||
onError: String | Array
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -9,6 +9,6 @@ defineProps({
|
||||
<p v-if="onError"
|
||||
class="mt-1 pl-2 text-xs text-red-500 dark:text-red-300"
|
||||
>
|
||||
{{onError}}
|
||||
{{ Array.isArray(onError) ? onError[0] : onError }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
@ -9,8 +9,8 @@ defineProps({
|
||||
|
||||
<template>
|
||||
<label v-if="title"
|
||||
:for="id"
|
||||
class="block text-sm font-medium text-page-t dark:text-page-dt"
|
||||
:for="id"
|
||||
>
|
||||
{{ $t(title) }}
|
||||
<span v-if="required"
|
||||
|
||||
@ -12,12 +12,12 @@ const emit = defineEmits([
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
class: String,
|
||||
required: Boolean,
|
||||
accept: {
|
||||
default: 'image/png, image/jpeg',
|
||||
type: String
|
||||
},
|
||||
class: String,
|
||||
required: Boolean,
|
||||
title: {
|
||||
default: 'photo.title',
|
||||
type: String
|
||||
@ -60,17 +60,17 @@ const updatePhotoPreview = () => {
|
||||
<div class="col-span-6">
|
||||
<input
|
||||
ref="photoInput"
|
||||
type="file"
|
||||
class="hidden"
|
||||
type="file"
|
||||
:accept="accept"
|
||||
:required="required"
|
||||
@change="updatePhotoPreview"
|
||||
>
|
||||
<Label
|
||||
class="dark:text-gray-800"
|
||||
id="image_file"
|
||||
:title="title"
|
||||
class="dark:text-gray-800"
|
||||
:required="required"
|
||||
:title="title"
|
||||
/>
|
||||
<div v-show="! photoPreview" class="mt-2">
|
||||
<!-- si existe una imagen cargada, entonces se muestra en este slot -->
|
||||
@ -79,9 +79,9 @@ const updatePhotoPreview = () => {
|
||||
<div v-show="photoPreview" class="mt-2">
|
||||
<div v-if="fileType == 'application/pdf'" class="flex overflow-hidden max-w-full">
|
||||
<GoogleIcon
|
||||
:title="$t('crud.edit')"
|
||||
class="text-gray-400"
|
||||
name="picture_as_pdf"
|
||||
:title="$t('crud.edit')"
|
||||
outline
|
||||
/>
|
||||
<div class="ml-2 font-bold text-gray-400 flex-1">
|
||||
@ -90,17 +90,17 @@ const updatePhotoPreview = () => {
|
||||
</div>
|
||||
<div v-else>
|
||||
<span
|
||||
:class="class"
|
||||
class="block rounded-lg h-40 bg-cover bg-no-repeat bg-center"
|
||||
:class="class"
|
||||
:style="'background-image: url(\'' + photoPreview + '\');'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<SecondaryButton
|
||||
v-text="$t('photo.new')"
|
||||
class="mt-2 mr-2"
|
||||
type="button"
|
||||
v-text="$t('photo.new')"
|
||||
@click.prevent="selectNewPhoto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -20,7 +20,7 @@ const props = defineProps({
|
||||
class: String,
|
||||
id: String,
|
||||
modelValue: Number | String,
|
||||
onError: String,
|
||||
onError: String | Array,
|
||||
placeholder: String,
|
||||
required: Boolean,
|
||||
title: String,
|
||||
@ -32,11 +32,6 @@ const props = defineProps({
|
||||
|
||||
const input = ref(null);
|
||||
|
||||
/** Exposiciones */
|
||||
defineExpose({
|
||||
focus: () => input.value.focus()
|
||||
});
|
||||
|
||||
/** Propiedades calculadas */
|
||||
const autoId = computed(() => {
|
||||
return (props.id)
|
||||
@ -52,6 +47,11 @@ const autoTitle = computed(() => {
|
||||
return props.id;
|
||||
});
|
||||
|
||||
/** Exposiciones */
|
||||
defineExpose({
|
||||
focus: () => input.value.focus()
|
||||
});
|
||||
|
||||
/** Ciclos */
|
||||
onMounted(() => {
|
||||
if (input.value.hasAttribute('autofocus')) {
|
||||
@ -68,14 +68,14 @@ onMounted(() => {
|
||||
:title="autoTitle"
|
||||
/>
|
||||
<input
|
||||
:id="autoId"
|
||||
class="input-primary"
|
||||
:placeholder="placeholder"
|
||||
v-bind="$attrs"
|
||||
ref="input"
|
||||
class="input-primary"
|
||||
:id="autoId"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
:type="type"
|
||||
:value="modelValue"
|
||||
v-bind="$attrs"
|
||||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
>
|
||||
<Error
|
||||
|
||||
@ -21,7 +21,7 @@ const props = defineProps({
|
||||
id: String,
|
||||
icon: String,
|
||||
modelValue: Number | String,
|
||||
onError: String,
|
||||
onError: String | Array,
|
||||
placeholder: String,
|
||||
required: Boolean,
|
||||
title: String,
|
||||
@ -33,16 +33,13 @@ const props = defineProps({
|
||||
|
||||
const input = ref(null);
|
||||
|
||||
/**
|
||||
* Propiedades calculadas
|
||||
*/
|
||||
/** Propiedades computadas */
|
||||
const autoId = computed(() => {
|
||||
return (props.id)
|
||||
? props.id
|
||||
: uuidv4()
|
||||
})
|
||||
|
||||
/** Propiedades computadas */
|
||||
const value = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
@ -73,9 +70,9 @@ onMounted(() => {
|
||||
/>
|
||||
<input
|
||||
ref="input"
|
||||
v-model="value"
|
||||
v-bind="$attrs"
|
||||
class="pl-2 w-full outline-none border-none bg-transparent"
|
||||
v-model="value"
|
||||
:id="autoId"
|
||||
:placeholder="placeholder"
|
||||
:type="type"
|
||||
|
||||
@ -14,25 +14,25 @@ const emit = defineEmits([
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
customLabel: String,
|
||||
trackBy: {
|
||||
default: 'id',
|
||||
type: String
|
||||
},
|
||||
disabled: Boolean,
|
||||
label: {
|
||||
default: 'name',
|
||||
type: String
|
||||
},
|
||||
modelValue: String | Number,
|
||||
title: String,
|
||||
multiple: Boolean,
|
||||
onError: String | Array,
|
||||
options: Object,
|
||||
onError: String,
|
||||
placeholder: {
|
||||
default: 'Buscar ...',
|
||||
type: String
|
||||
},
|
||||
required: Boolean,
|
||||
multiple: Boolean,
|
||||
disabled: Boolean
|
||||
trackBy: {
|
||||
default: 'id',
|
||||
type: String
|
||||
},
|
||||
title: String,
|
||||
});
|
||||
|
||||
const multiselect = ref();
|
||||
@ -56,8 +56,8 @@ defineExpose({
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<Label
|
||||
:title="title"
|
||||
:required="required"
|
||||
:title="title"
|
||||
/>
|
||||
<VueMultiselect
|
||||
ref="multiselect"
|
||||
@ -69,8 +69,8 @@ defineExpose({
|
||||
:close-on-select="true"
|
||||
:custom-label="customLabel"
|
||||
:disabled="disabled"
|
||||
:multiple="multiple"
|
||||
:label="label"
|
||||
:multiple="multiple"
|
||||
:options="options"
|
||||
:placeholder="placeholder"
|
||||
:preserve-search="true"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue'
|
||||
import Label from './Elements/Label.vue';
|
||||
@ -12,13 +12,13 @@ const emit = defineEmits([
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
modelValue:Object|String,
|
||||
class: String,
|
||||
required: Boolean,
|
||||
accept: {
|
||||
default: 'image/png, image/jpeg',
|
||||
type: String
|
||||
},
|
||||
class: String,
|
||||
modelValue:Object|String,
|
||||
required: Boolean,
|
||||
title: {
|
||||
default: 'photo.title',
|
||||
type: String
|
||||
@ -78,9 +78,9 @@ const updatePhotoPreview = () => {
|
||||
<div v-show="photoPreview" class="mt-2">
|
||||
<div class="flex overflow-hidden max-w-full">
|
||||
<GoogleIcon
|
||||
:title="$t('crud.edit')"
|
||||
class="text-gray-400"
|
||||
name="picture_as_pdf"
|
||||
:title="$t('crud.edit')"
|
||||
outline
|
||||
/>
|
||||
<div class="ml-2 font-bold text-gray-400 flex-1">
|
||||
@ -94,9 +94,9 @@ const updatePhotoPreview = () => {
|
||||
</div>
|
||||
</div>
|
||||
<SecondaryButton
|
||||
v-text="$t('files.select')"
|
||||
class="mt-2 mr-2"
|
||||
type="button"
|
||||
v-text="$t('files.select')"
|
||||
@click.prevent="selectNewPhoto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -16,15 +16,15 @@ const props = defineProps({
|
||||
Boolean
|
||||
]
|
||||
},
|
||||
disabled: Boolean,
|
||||
title: {
|
||||
default: lang('active'),
|
||||
default: Lang('active'),
|
||||
type: String
|
||||
},
|
||||
value: {
|
||||
default: null,
|
||||
type: String
|
||||
},
|
||||
disabled: Boolean
|
||||
}
|
||||
});
|
||||
|
||||
const uuid = uuidv4()
|
||||
@ -34,7 +34,6 @@ const proxyChecked = computed({
|
||||
get() {
|
||||
return props.checked;
|
||||
},
|
||||
|
||||
set(val) {
|
||||
emit('update:checked', val);
|
||||
},
|
||||
@ -45,16 +44,24 @@ const proxyChecked = computed({
|
||||
<div class="flex items-center">
|
||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input
|
||||
.id="uuid"
|
||||
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
|
||||
type="checkbox"
|
||||
name="toggle"
|
||||
:value="value"
|
||||
v-model="proxyChecked"
|
||||
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"
|
||||
name="toggle"
|
||||
type="checkbox"
|
||||
:id="uuid"
|
||||
:disabled="disabled"
|
||||
:value="value"
|
||||
/>
|
||||
<label
|
||||
class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"
|
||||
:for="uuid"
|
||||
/>
|
||||
<label :for="uuid" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
</div>
|
||||
<label :for="uuid" class="text-xs text-gray-700">{{ $t(title) }}</label>
|
||||
<label
|
||||
class="text-xs text-gray-700"
|
||||
:for="uuid"
|
||||
>
|
||||
{{ $t(title) }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
@ -64,15 +64,17 @@ onMounted(() => {
|
||||
:title="autoTitle"
|
||||
/>
|
||||
<textarea
|
||||
:id="autoId"
|
||||
class="input-primary"
|
||||
:placeholder="placeholder"
|
||||
ref="input"
|
||||
v-bind="$attrs"
|
||||
class="input-primary"
|
||||
:id="autoId"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
:value="modelValue"
|
||||
v-bind="$attrs"
|
||||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
></textarea>
|
||||
<Error :onError="onError"/>
|
||||
<Error
|
||||
:onError="onError"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -65,10 +65,10 @@ function add() {
|
||||
itemA.value = null
|
||||
itemB.value = null
|
||||
} else {
|
||||
Notify.warning(lang('todo.uniqueSub.b.required', {name:lang('subclassification')}))
|
||||
Notify.warning(Lang('todo.uniqueSub.b.required', {name:Lang('subclassification')}))
|
||||
}
|
||||
} else {
|
||||
Notify.warning(lang('todo.uniqueSub.a.required', {name:lang('classification')}))
|
||||
Notify.warning(Lang('todo.uniqueSub.a.required', {name:Lang('classification')}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,18 +115,23 @@ onMounted(() => {
|
||||
<p>{{ title }}</p>
|
||||
<div class="w-full grid gap-2 grid-cols-2 dark:bg-primary-d/50 rounded-md">
|
||||
<Selectable
|
||||
:title="itemATitle"
|
||||
v-model="itemA"
|
||||
:title="itemATitle"
|
||||
:options="itemsAUnselected"
|
||||
/>
|
||||
<Input
|
||||
:title="itemBTitle"
|
||||
v-model="itemB"
|
||||
:title="itemBTitle"
|
||||
:type="type"
|
||||
@keyup.enter="add"
|
||||
/>
|
||||
<div class="col-span-2 flex justify-center">
|
||||
<PrimaryButton type="button" @click="add">{{ $t('add') }}</PrimaryButton>
|
||||
<PrimaryButton
|
||||
type="button"
|
||||
@click="add"
|
||||
>
|
||||
{{ $t('add') }}
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
<div class="col-span-2 text-sm">
|
||||
<p><b>{{ $t('items') }}</b> ({{ values.length }})</p>
|
||||
@ -136,18 +141,17 @@ onMounted(() => {
|
||||
<div class="relative rounded border border-primary/50">
|
||||
<div class="grid gap-2 grid-cols-2 w-full items-center p-2 dark:bg-primary-d/50">
|
||||
<Input
|
||||
:title="itemATitle"
|
||||
v-model="item.item.name"
|
||||
:title="itemATitle"
|
||||
disabled
|
||||
/>
|
||||
<Input
|
||||
:title="itemBTitle"
|
||||
v-model="item.value"
|
||||
:title="itemBTitle"
|
||||
/>
|
||||
</div>
|
||||
<div class="absolute right-1 top-1">
|
||||
<GoogleIcon
|
||||
type="button"
|
||||
class="btn-icon-primary"
|
||||
name="close"
|
||||
@click="remove(index, item.item)"
|
||||
|
||||
@ -13,9 +13,9 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
/** Propiedades */
|
||||
const check = ref(false);
|
||||
const filterMessages = ref(false);
|
||||
|
||||
/** Métodos */
|
||||
const selectThisPage = () => props.inboxCtl.onSelectAll(props.items, false);
|
||||
const unselectThisPage = () => props.inboxCtl.onUnselectAll(props.items)
|
||||
|
||||
@ -60,11 +60,11 @@ const search = url => props.searcherCtl.searchWithInboxPagination(url);
|
||||
class="relative flex items-center px-0.5 space-x-0.5"
|
||||
>
|
||||
<button class="px-2 pt-1" @click="filterMessages = !filterMessages">
|
||||
<GoogleIcon
|
||||
class="text-xl"
|
||||
name="checklist"
|
||||
outline
|
||||
/>
|
||||
<GoogleIcon
|
||||
class="text-xl"
|
||||
name="checklist"
|
||||
outline
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
@click.away="filterMessages = false"
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
// Propiedades
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
inboxCtl: Object, //Controller
|
||||
item: Object,
|
||||
selecteds: Object
|
||||
})
|
||||
|
||||
// Variables generales
|
||||
const check = ref(false);
|
||||
const messageHover = ref(false);
|
||||
|
||||
// Métodos
|
||||
/** Métodos */
|
||||
const select = () => (!check.value)
|
||||
? props.inboxCtl.onSelectOne(props.item)
|
||||
: props.inboxCtl.onUnselectOne(props.item);
|
||||
@ -35,9 +34,9 @@ const selected = computed(() => {
|
||||
>
|
||||
<div class="pr-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="focus:ring-0 border-2 border-gray-400"
|
||||
v-model="check"
|
||||
class="focus:ring-0 border-2 border-gray-400"
|
||||
type="checkbox"
|
||||
@click="select"
|
||||
>
|
||||
</div>
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { RouterLink } from 'vue-router';
|
||||
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
icon: String,
|
||||
counter: Number,
|
||||
@ -15,6 +16,7 @@ const props = defineProps({
|
||||
title: String,
|
||||
});
|
||||
|
||||
/** Propiedades computadas */
|
||||
const classes = computed(() => {
|
||||
let status = route().current(props.to, props.toParam)
|
||||
? 'bg-secondary bg-opacity-30'
|
||||
@ -22,15 +24,18 @@ const classes = computed(() => {
|
||||
|
||||
return ` text-primary flex items-center justify-between py-1.5 px-4 rounded cursor-pointer ${status} transition`
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li>
|
||||
<Link v-if="to" :href="route(to, toParam)" :class="classes">
|
||||
<RouterLink
|
||||
v-if="to"
|
||||
:class="classes"
|
||||
:to="to"
|
||||
>
|
||||
<span class="flex items-center space-x-2">
|
||||
<GoogleIcon
|
||||
class="text-lg"
|
||||
class="text-lg"
|
||||
:name="icon"
|
||||
outline
|
||||
/>
|
||||
@ -41,6 +46,6 @@ const classes = computed(() => {
|
||||
<span v-if="counter > 0" class="bg-primary text-gray-100 font-bold px-2 py-0.5 text-xs rounded-lg">
|
||||
{{ counter }}
|
||||
</span>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</li>
|
||||
</template>
|
||||
@ -1,9 +1,10 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { RouterLink } from 'vue-router';
|
||||
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
icon: String,
|
||||
to: String,
|
||||
@ -14,15 +15,18 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
/** Propiedades computadas */
|
||||
const classes = computed(() => {
|
||||
return `inbox-menu-button-${props.type}`;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-16 flex items-center pr-2">
|
||||
<Link :href="route(to)" :class="classes">
|
||||
<RouterLink
|
||||
:class="classes"
|
||||
:to="to"
|
||||
>
|
||||
<span class="flex items-center space-x-2 ">
|
||||
<GoogleIcon
|
||||
class="text-lg text-white font-bold"
|
||||
@ -33,6 +37,6 @@ const classes = computed(() => {
|
||||
{{ title }}
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { onBeforeMount, onMounted } from 'vue';
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { bootPermissions } from '@Plugins/RolePermission.js';
|
||||
import { reloadApp } from '@Services/Page';
|
||||
import useDarkMode from '@Stores/DarkMode'
|
||||
import useLeftSidebar from '@Stores/LeftSidebar'
|
||||
import useNotificationSidebar from '@Stores/NotificationSidebar'
|
||||
@ -18,15 +18,12 @@ const notificationSidebar = useNotificationSidebar();
|
||||
/** Propiedades */
|
||||
defineProps({
|
||||
title: String,
|
||||
titlePage: {
|
||||
default: true,
|
||||
type: Boolean
|
||||
}
|
||||
});
|
||||
|
||||
/** Ciclos */
|
||||
onBeforeMount(() => {
|
||||
bootPermissions()
|
||||
reloadApp();
|
||||
})
|
||||
|
||||
onMounted(()=> {
|
||||
@ -36,7 +33,6 @@ onMounted(()=> {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head :title="title" />
|
||||
<div class="flex w-full h-screen bg-page text-page-t dark:bg-page-d dark:text-page-dt">
|
||||
<LeftSidebar
|
||||
@open="leftSidebar.toggle()"
|
||||
@ -56,13 +52,7 @@ onMounted(()=> {
|
||||
/>
|
||||
</div>
|
||||
<main class="flex h-full justify-center md:p-2">
|
||||
<div class="mt-14 md:mt-0 w-full shadow-lg dark:shadow-sm md:dark:shadow-white h-[calc(100vh-4.5rem)] px-2 md:rounded-lg md:overflow-y-auto md:overflow-x-auto transition-colors duration-300">
|
||||
<div v-if="titlePage" class="flex w-full justify-center">
|
||||
<h2
|
||||
class="font-bold text-xl uppercase"
|
||||
v-text="title"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-14 md:mt-0 w-full shadow-lg dark:shadow-sm md:dark:shadow-white h-[calc(100vh-4.5rem)] px-2 md:rounded-lg overflow-y-auto overflow-x-auto transition-colors duration-300">
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { APP_VERSION, APP_COPYRIGHT } from '@/config.js'
|
||||
import useDarkMode from '@Stores/DarkMode'
|
||||
|
||||
import Logo from '@Holos/Logo.vue'
|
||||
import IconButton from '@Holos/Button/Icon.vue'
|
||||
import Logo from '@Holos/Logo.vue';
|
||||
|
||||
/** Definidores */
|
||||
const darkMode = useDarkMode()
|
||||
@ -14,17 +14,14 @@ defineProps({
|
||||
title: String
|
||||
})
|
||||
|
||||
/**
|
||||
* Ciclos
|
||||
*/
|
||||
/** Ciclos */
|
||||
onMounted(() => {
|
||||
darkMode.boot()
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head :title="title" />
|
||||
<div class="h-screen flex">
|
||||
<div class="h-screen flex bg-primary dark:bg-primary-d">
|
||||
<div
|
||||
class="relative flex w-full lg:w-full justify-around items-center with-transition"
|
||||
:class="{'app-bg-light':darkMode.isLight,'app-bg-dark':darkMode.isDark}"
|
||||
@ -32,13 +29,13 @@ onMounted(() => {
|
||||
<header class="absolute top-0 flex w-full h-8 px-1 items-center justify-end text-white">
|
||||
<div>
|
||||
<IconButton v-if="darkMode.isLight"
|
||||
:title="$t('app.theme.light')"
|
||||
icon="light_mode"
|
||||
:title="$t('app.theme.light')"
|
||||
@click="darkMode.applyDark()"
|
||||
/>
|
||||
<IconButton v-else
|
||||
:title="$t('app.theme.dark')"
|
||||
icon="dark_mode"
|
||||
:title="$t('app.theme.dark')"
|
||||
@click="darkMode.applyLight()"
|
||||
/>
|
||||
</div>
|
||||
@ -46,27 +43,27 @@ onMounted(() => {
|
||||
<div class="flex w-full flex-col items-center justify-center space-y-2">
|
||||
<div class="flex space-x-2 items-center justify-start text-white">
|
||||
<Logo
|
||||
class="text-lg inline-flex"
|
||||
/>
|
||||
class="text-lg inline-flex"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<main class="bg-white/10 w-full backdrop-blur-sm text-white px-4 py-8 rounded-md max-w-80">
|
||||
<slot />
|
||||
<main class="bg-white/10 w-full backdrop-blur-sm text-white px-4 py-4 rounded-md max-w-80">
|
||||
<RouterView />
|
||||
</main>
|
||||
|
||||
<footer class="absolute bottom-0 flex w-full h-8 px-4 items-center justify-between bg-primary dark:bg-primary-d backdrop-blur-sm text-white transition-colors duration-global">
|
||||
<footer class="absolute bottom-0 flex w-full h-8 px-4 items-center justify-between bg-primary dark:bg-primary-d backdrop-blur-md text-white transition-colors duration-global">
|
||||
<div>
|
||||
<span>
|
||||
©2024 {{ $page.props.copyright }}
|
||||
©2024 {{ APP_COPYRIGHT }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
Versión {{ $page.version }}
|
||||
Versión {{ APP_VERSION }}
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { APP_VERSION, APP_COPYRIGHT } from '@/config.js'
|
||||
import useDarkMode from '@Stores/DarkMode'
|
||||
|
||||
import IconButton from '@Holos/Button/Icon.vue'
|
||||
import Logo from '@Holos/Logo.vue';
|
||||
import IconButton from '../Button/Icon.vue'
|
||||
import Logo from '../Logo.vue';
|
||||
|
||||
/** Definidores */
|
||||
const darkMode = useDarkMode()
|
||||
@ -14,16 +14,13 @@ defineProps({
|
||||
title: String
|
||||
})
|
||||
|
||||
/**
|
||||
* Ciclos
|
||||
*/
|
||||
/** Ciclos */
|
||||
onMounted(() => {
|
||||
darkMode.boot()
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head :title="title" />
|
||||
<div class="min-h-screen flex">
|
||||
<div
|
||||
class="relative flex w-full lg:w-full justify-around items-start with-transition"
|
||||
@ -32,13 +29,13 @@ onMounted(() => {
|
||||
<header class="absolute top-0 flex w-full h-8 px-1 items-center justify-end text-white">
|
||||
<div>
|
||||
<IconButton v-if="darkMode.isLight"
|
||||
:title="$t('app.theme.light')"
|
||||
icon="light_mode"
|
||||
:title="$t('app.theme.light')"
|
||||
@click="darkMode.applyDark()"
|
||||
/>
|
||||
<IconButton v-else
|
||||
:title="$t('app.theme.dark')"
|
||||
icon="dark_mode"
|
||||
:title="$t('app.theme.dark')"
|
||||
@click="darkMode.applyLight()"
|
||||
/>
|
||||
</div>
|
||||
@ -50,19 +47,19 @@ onMounted(() => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<main class="bg-white/10 w-full mx-auto sm:max-w-2xl backdrop-blur-sm text-white px-4 py-8 rounded-md">
|
||||
<main class="bg-white/10 w-full mx-auto sm:max-w-2xl backdrop-blur-sm text-white px-4 py-8 rounded-md">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<footer class="absolute bottom-0 flex w-full h-8 px-4 items-center justify-between bg-primary dark:bg-primary-d backdrop-blur-sm text-white transition-colors duration-global">
|
||||
<div>
|
||||
<span>
|
||||
©2024 {{ $page.props.copyright }}
|
||||
©{{ APP_COPYRIGHT }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
Versión {{ $page.version }}
|
||||
Versión {{ APP_VERSION }}
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -1,9 +1,18 @@
|
||||
<script setup>
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
/** Definidores */
|
||||
const router = useRouter();
|
||||
|
||||
/** Métodos */
|
||||
const home = () => router.push(view({ name: 'index' }));
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<Link :href="'/'" class="flex w-full justify-center items-center space-x-2">
|
||||
<div
|
||||
class="flex w-full justify-center items-center space-x-2 cursor-pointer"
|
||||
@click="home"
|
||||
>
|
||||
<img src="/images/logo.png" class="h-20" />
|
||||
</Link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -13,7 +13,7 @@ defineEmits([
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
title: {
|
||||
default: lang('delete.title'),
|
||||
default: Lang('delete.title'),
|
||||
type: String
|
||||
}
|
||||
});
|
||||
@ -42,12 +42,12 @@ const props = defineProps({
|
||||
<div class="space-x-2">
|
||||
<slot name="buttons" />
|
||||
<DangerButton
|
||||
@click="$emit('destroy')"
|
||||
v-text="$t('delete.title')"
|
||||
@click="$emit('destroy')"
|
||||
/>
|
||||
<SecondaryButton
|
||||
@click="$emit('close')"
|
||||
v-text="$t('cancel')"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -13,7 +13,7 @@ const emit = defineEmits([
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
title: {
|
||||
default: lang('edit'),
|
||||
default: Lang('edit'),
|
||||
type: String
|
||||
}
|
||||
});
|
||||
@ -38,12 +38,12 @@ const props = defineProps({
|
||||
<div class="space-x-2">
|
||||
<slot name="buttons" />
|
||||
<PrimaryButton
|
||||
@click="$emit('update')"
|
||||
v-text="$t('update')"
|
||||
@click="$emit('update')"
|
||||
/>
|
||||
<SecondaryButton
|
||||
@click="$emit('close')"
|
||||
v-text="$t('close')"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -14,7 +14,7 @@ const props = defineProps({
|
||||
editable: Boolean,
|
||||
show: Boolean,
|
||||
title: {
|
||||
default: lang('details'),
|
||||
default: Lang('details'),
|
||||
type: String
|
||||
}
|
||||
});
|
||||
@ -38,14 +38,13 @@ const props = defineProps({
|
||||
<template #footer>
|
||||
<div class="space-x-2">
|
||||
<slot name="buttons" />
|
||||
<PrimaryButton
|
||||
v-if="editable"
|
||||
@click="$emit('edit')"
|
||||
<PrimaryButton v-if="editable"
|
||||
v-text="$t('update')"
|
||||
@click="$emit('edit')"
|
||||
/>
|
||||
<SecondaryButton
|
||||
@click="$emit('close')"
|
||||
v-text="$t('close')"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
<script setup>
|
||||
import { router } from '@inertiajs/vue3';
|
||||
import { api } from '@Services/Api.js';
|
||||
|
||||
import DestroyModal from '../Destroy.vue';
|
||||
import Header from '../Elements/Header.vue';
|
||||
|
||||
/** Eventos */
|
||||
const emit = defineEmits([
|
||||
'close',
|
||||
'switchModal'
|
||||
'close',
|
||||
'update'
|
||||
]);
|
||||
|
||||
/** Propiedades */
|
||||
@ -18,15 +18,14 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
/** Métodos */
|
||||
const destroy = (id) => router.delete(props.to(id), {
|
||||
preserveScroll: true,
|
||||
const destroy = (id) => api.delete(props.apiTo(id), {
|
||||
onSuccess: () => {
|
||||
props.model.pop;
|
||||
Notify.success(lang('deleted'));
|
||||
Notify.success(Lang('deleted'));
|
||||
emit('close');
|
||||
emit('update');
|
||||
},
|
||||
onError: () => {
|
||||
Notify.info(lang('notFound'));
|
||||
Notify.info(Lang('notFound'));
|
||||
emit('close');
|
||||
}
|
||||
});
|
||||
@ -39,8 +38,8 @@ const destroy = (id) => router.delete(props.to(id), {
|
||||
@destroy="destroy(model.id)"
|
||||
>
|
||||
<Header
|
||||
:title="model.name"
|
||||
:subtitle="model.full_last_name"
|
||||
:title="model.name"
|
||||
/>
|
||||
</DestroyModal>
|
||||
</template>
|
||||
@ -1,74 +0,0 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
import GoogleIcon from '@/components/Shared/GoogleIcon.vue'
|
||||
|
||||
const emit = defineEmits([
|
||||
'close'
|
||||
])
|
||||
|
||||
const props = defineProps({
|
||||
item: Object,
|
||||
})
|
||||
|
||||
const isOpen = ref(false)
|
||||
|
||||
const close = () => {
|
||||
isOpen.value = false
|
||||
|
||||
setTimeout(() => {
|
||||
emit('close')
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const typeClasses = computed(() => {
|
||||
let nameClass = 'w-64 rounded-md text-white p-2';
|
||||
|
||||
switch (props.item.type) {
|
||||
case 'info':
|
||||
nameClass += ' bg-blue-500'
|
||||
break;
|
||||
case 'success':
|
||||
nameClass += ' bg-green-500'
|
||||
break;
|
||||
case 'warning':
|
||||
nameClass += ' bg-yellow-500'
|
||||
break;
|
||||
case 'error':
|
||||
nameClass += ' bg-red-500'
|
||||
break;
|
||||
default:
|
||||
nameClass += ' bg-blue-500'
|
||||
break;
|
||||
}
|
||||
|
||||
return nameClass
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
isOpen.value = true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition
|
||||
enter-active-class="ease-out duration-300"
|
||||
enter-from-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enter-to-class="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave-active-class="ease-in duration-300"
|
||||
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"
|
||||
>
|
||||
<div v-show="isOpen" :class="typeClasses">
|
||||
<div class="flex justify-between items-center">
|
||||
<h4 class="font-bold text-sm truncate">{{ item.title }}</h4>
|
||||
<GoogleIcon
|
||||
class="cursor-pointer"
|
||||
name="close"
|
||||
@click="close()"
|
||||
/>
|
||||
</div>
|
||||
<h4 class="text-sm ">{{ item.message }}</h4>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
@ -1,22 +1,31 @@
|
||||
<script setup>
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { RouterLink } from 'vue-router';
|
||||
|
||||
import IconButton from '@Holos/Button/Icon.vue'
|
||||
|
||||
defineProps({
|
||||
title: String
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full justify-end py-[0.31rem] border-y-2 border-page-t dark:border-page-dt">
|
||||
<div id="buttons" class="flex items-center space-x-2 text-sm py-0.5">
|
||||
<slot />
|
||||
<Link :href="route('dashboard.index')">
|
||||
<div v-if="title" class="flex w-full justify-center">
|
||||
<h2
|
||||
class="font-bold text-xl uppercase"
|
||||
v-text="title"
|
||||
/>
|
||||
</div>
|
||||
<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-2 text-sm">
|
||||
<RouterLink :to="$view({ name: 'index' })">
|
||||
<IconButton
|
||||
:title="$t('home')"
|
||||
class="text-white"
|
||||
icon="home"
|
||||
filled
|
||||
/>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,29 +1,38 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
|
||||
import IconButton from '@Holos/Button/Icon.vue'
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
|
||||
/** Eventos */
|
||||
const emit = defineEmits([
|
||||
'search'
|
||||
]);
|
||||
|
||||
const query = ref('');
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
placeholder: {
|
||||
default: lang('search'),
|
||||
default: Lang('search'),
|
||||
type: String
|
||||
}
|
||||
})
|
||||
|
||||
const query = ref('');
|
||||
|
||||
/** Métodos */
|
||||
const search = () => {
|
||||
emit('search', query.value);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex w-full justify-between items-center border-y-2 border-page-t dark:border-page-dt">
|
||||
<div v-if="title" class="flex w-full justify-center">
|
||||
<h2
|
||||
class="font-bold text-xl uppercase"
|
||||
v-text="title"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex w-full justify-between items-center border-y-2 border-page-t dark:border-page-dt">
|
||||
<div>
|
||||
<div class="relative py-1 z-0">
|
||||
<div @click="search" class="absolute inset-y-0 right-2 flex items-center pl-3 cursor-pointer text-gray-700 hover:scale-110 hover:text-danger">
|
||||
@ -47,14 +56,14 @@ const search = () => {
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 text-sm" id="buttons">
|
||||
<slot />
|
||||
<Link :href="route('dashboard.index')">
|
||||
<RouterLink :to="$view({name:'index'})">
|
||||
<IconButton
|
||||
:title="$t('home')"
|
||||
class="text-white"
|
||||
icon="home"
|
||||
filled
|
||||
/>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,15 +1,13 @@
|
||||
<script setup>
|
||||
import { onMounted} from 'vue';
|
||||
import { router } from '@inertiajs/vue3';
|
||||
import { resetPermissions } from '@/Plugins/RolePermission';
|
||||
import { logout } from '@Services/Page';
|
||||
import useDarkMode from '@Stores/DarkMode'
|
||||
import useLeftSidebar from '@Stores/LeftSidebar'
|
||||
import useNotificationSidebar from '@Stores/NotificationSidebar'
|
||||
import useNotifier from '@Stores/Notifier'
|
||||
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
import Dropdown from '../Dropdown.vue';
|
||||
import DropdownLink from '../DropdownLink.vue';
|
||||
// import NotificationLink from '.NotificationLink.vue';
|
||||
|
||||
/** Eventos */
|
||||
const emit = defineEmits([
|
||||
@ -21,61 +19,45 @@ const darkMode = useDarkMode()
|
||||
const leftSidebar = useLeftSidebar()
|
||||
const notificationSidebar = useNotificationSidebar()
|
||||
const notifier = useNotifier()
|
||||
|
||||
// Métodos
|
||||
const logout = () => {
|
||||
resetPermissions()
|
||||
|
||||
router.post(route('logout'), {}, {
|
||||
onBefore: () => {
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** Ciclos */
|
||||
onMounted(()=>{
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header
|
||||
class="fixed px-2 w-[calc(100vw)] bg-transparent transition-all duration-300 z-50"
|
||||
:class="{'md:w-[calc(100vw-16rem)]':leftSidebar.isOpened,'md:w-[calc(100vw)]':!leftSidebar.isClosed}"
|
||||
>
|
||||
<div class="my-2 flex px-6 items-center justify-between h-[2.75rem] rounded-lg bg-primary dark:bg-primary-d text-white z-20 ">
|
||||
<GoogleIcon
|
||||
:title="$t('menu')"
|
||||
class="text-2xl mt-1 z-50"
|
||||
name="list"
|
||||
@click="emit('open')"
|
||||
outline
|
||||
/>
|
||||
>
|
||||
<div class="my-2 flex px-6 items-center justify-between h-[2.75rem] rounded-lg bg-primary dark:bg-primary-d text-white z-20 ">
|
||||
<GoogleIcon
|
||||
class="text-2xl mt-1 z-50"
|
||||
name="list"
|
||||
:title="$t('menu')"
|
||||
@click="emit('open')"
|
||||
outline
|
||||
/>
|
||||
<div class="flex w-fit justify-end items-center h-14 header-right">
|
||||
<ul class="flex items-center space-x-2">
|
||||
<li class="flex items-center">
|
||||
<GoogleIcon
|
||||
:title="$t('notifications.title')"
|
||||
class="text-xl mt-1"
|
||||
name="notifications"
|
||||
:title="$t('notifications.title')"
|
||||
@click="notificationSidebar.toggle()"
|
||||
/>
|
||||
<span class="text-xs">{{ notifier.counter }}</span>
|
||||
</li>
|
||||
<li v-if="darkMode.isDark">
|
||||
<GoogleIcon
|
||||
:title="$t('notifications.title')"
|
||||
class="text-xl mt-1"
|
||||
name="light_mode"
|
||||
:title="$t('notifications.title')"
|
||||
@click="darkMode.applyLight()"
|
||||
/>
|
||||
</li>
|
||||
<li v-else>
|
||||
<GoogleIcon
|
||||
:title="$t('notifications.title')"
|
||||
class="text-xl mt-1"
|
||||
name="dark_mode"
|
||||
:title="$t('notifications.title')"
|
||||
@click="darkMode.applyDark()"
|
||||
/>
|
||||
</li>
|
||||
@ -85,27 +67,23 @@ onMounted(()=>{
|
||||
<template #trigger>
|
||||
<div class="flex space-x-4">
|
||||
<button
|
||||
v-if="$page.props.jetstream.managesProfilePhotos"
|
||||
:title="$t('users.menu')"
|
||||
class="flex items-center space-x-4 text-sm border-2 border-transparent rounded-full focus:outline-none transition"
|
||||
:title="$t('users.menu')"
|
||||
>
|
||||
<img
|
||||
class="h-8 w-8 rounded-full object-cover"
|
||||
:alt="$page.props.auth.user.name"
|
||||
:src="$page.props.auth.user.profile_photo_url"
|
||||
>
|
||||
<img
|
||||
class="h-8 w-8 rounded-full object-cover"
|
||||
:alt="$page.user.name"
|
||||
:src="$page.user.profile_photo_url"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="text-center block px-4 py-2 text-sm border-b truncate">
|
||||
{{ $page.props.auth.user.name }}
|
||||
{{ $page.user.name }}
|
||||
</div>
|
||||
<DropdownLink :href="route('profile.show')">
|
||||
{{$t('profile')}}
|
||||
</DropdownLink>
|
||||
<DropdownLink v-if="$page.props.jetstream.hasApiFeatures" :href="route('api-tokens.index')">
|
||||
API Tokens
|
||||
<DropdownLink to="profile.show">
|
||||
{{$t('profile')}}
|
||||
</DropdownLink>
|
||||
<div class="border-t border-gray-100" />
|
||||
<form @submit.prevent="logout">
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
|
||||
const props = defineProps({
|
||||
as: String,
|
||||
href: String,
|
||||
icon: {
|
||||
default: 'notifications_active',
|
||||
type: String
|
||||
},
|
||||
readAt: String
|
||||
});
|
||||
|
||||
const classes = computed(()=> {
|
||||
return 'inline-flex space-x-2 w-full px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition';
|
||||
});
|
||||
|
||||
const readed = computed(()=> {
|
||||
return (props.readAt)
|
||||
? 'text-primary'
|
||||
: 'text-warning';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button
|
||||
v-if="as == 'button'"
|
||||
:class="classes"
|
||||
type="button"
|
||||
>
|
||||
<GoogleIcon
|
||||
:class="readed"
|
||||
:name="icon"
|
||||
/>
|
||||
<slot />
|
||||
</button>
|
||||
|
||||
<a
|
||||
v-else-if="as =='a'"
|
||||
:class="classes"
|
||||
:href="href"
|
||||
>
|
||||
<GoogleIcon
|
||||
:class="readed"
|
||||
:name="icon"
|
||||
/>
|
||||
<slot />
|
||||
</a>
|
||||
|
||||
<Link
|
||||
v-else
|
||||
:class="classes"
|
||||
:href="href"
|
||||
>
|
||||
<GoogleIcon
|
||||
class="text-primary hover:text-secondary"
|
||||
:class="readed"
|
||||
:name="icon"
|
||||
/>
|
||||
<slot />
|
||||
</Link>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,17 +1,22 @@
|
||||
<script setup>
|
||||
import { APP_VERSION, APP_COPYRIGHT } from '@/config.js'
|
||||
import useLeftSidebar from '@Stores/LeftSidebar'
|
||||
|
||||
import Logo from '@Holos/Logo.vue';
|
||||
|
||||
/** Definidores */
|
||||
const leftSidebar = useLeftSidebar()
|
||||
|
||||
/** Eventos */
|
||||
const emit = defineEmits(['open']);
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
sidebar: Boolean
|
||||
});
|
||||
|
||||
const leftSidebar = useLeftSidebar()
|
||||
|
||||
const year = (new Date).getFullYear();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -37,10 +42,10 @@ const year = (new Date).getFullYear();
|
||||
</div>
|
||||
<div class="mb-4 px-5 space-y-1">
|
||||
<p class="block text-center text-xs">
|
||||
© {{year}} {{$page.props.copyright}}
|
||||
© {{year}} {{ APP_COPYRIGHT }}
|
||||
</p>
|
||||
<p class="text-center text-xs text-yellow-500 cursor-pointer">
|
||||
V{{$page.version}}
|
||||
V{{ APP_VERSION }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import useLeftSidebar from '@/Stores/LeftSidebar';
|
||||
import { RouterLink, useRoute } from 'vue-router';
|
||||
|
||||
import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
|
||||
import useLeftSidebar from '@Stores/LeftSidebar';
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
|
||||
/** Definidores */
|
||||
const leftSidebar = useLeftSidebar();
|
||||
const vroute = useRoute();
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
@ -16,7 +17,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const classes = computed(() => {
|
||||
let status = route().current(props.to)
|
||||
let status = props.to === vroute.name
|
||||
? 'bg-secondary/30 dark:bg-secondary-d/30 border-secondary dark:border-secondary-d'
|
||||
: 'border-transparent';
|
||||
|
||||
@ -32,8 +33,11 @@ const closeSidebar = () => {
|
||||
|
||||
<template>
|
||||
<li @click="closeSidebar()">
|
||||
<Link :href="route(to)" :class="classes">
|
||||
<span
|
||||
<RouterLink
|
||||
:class="classes"
|
||||
:to="$view({name:to})"
|
||||
>
|
||||
<span
|
||||
v-if="icon"
|
||||
class="inline-flex justify-center items-center ml-4 mr-2"
|
||||
>
|
||||
@ -45,11 +49,10 @@ const closeSidebar = () => {
|
||||
</span>
|
||||
<span
|
||||
v-if="name"
|
||||
v-text="$t(name)"
|
||||
class="text-sm tracking-wide truncate"
|
||||
>
|
||||
{{$t(name)}}
|
||||
</span>
|
||||
/>
|
||||
<slot />
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
@ -6,10 +6,8 @@ import useNotifier from '@Stores/Notifier'
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
import Item from './Notification/Item.vue';
|
||||
|
||||
/**
|
||||
* Definidores
|
||||
*/
|
||||
const notifier = useNotifier();
|
||||
/** Definidores */
|
||||
const notifier = useNotifier();
|
||||
const notificationSidebar = useNotificationSidebar()
|
||||
|
||||
/** Eventos */
|
||||
@ -20,9 +18,7 @@ const props = defineProps({
|
||||
sidebar: Boolean
|
||||
});
|
||||
|
||||
/**
|
||||
* Ciclos
|
||||
*/
|
||||
/** Ciclos */
|
||||
onMounted(() => {
|
||||
notifier.boot();
|
||||
});
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<script setup>
|
||||
import { getDateTime } from '@Controllers/DateController';
|
||||
import useNotifier from '@Stores/Notifier';
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
|
||||
/** Definidores */
|
||||
const notifier = useNotifier();
|
||||
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
|
||||
/** Propiedades */
|
||||
defineProps({
|
||||
notification: Object,
|
||||
});
|
||||
@ -30,35 +30,41 @@ defineProps({
|
||||
<div class="w-10 space-y-0">
|
||||
<template v-if="notification.user">
|
||||
<div class="w-10 h-10 bg-transparent rounded-full flex items-center justify-center">
|
||||
<img
|
||||
v-if="notification.user"
|
||||
class="rounded-full object-cover"
|
||||
:alt="notification.user.name"
|
||||
:src="notification.user.profile_photo_url"
|
||||
<img v-if="notification.user"
|
||||
class="rounded-full object-cover"
|
||||
:alt="notification.user.name"
|
||||
:src="notification.user.profile_photo_url"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="w-10 h-10 bg-secondary dark:bg-secondary-d rounded-xl flex items-center justify-center">
|
||||
<img
|
||||
v-if="notification.user"
|
||||
class="rounded-full object-cover"
|
||||
:alt="notification.user.name"
|
||||
:src="notification.user.profile_photo_url"
|
||||
<img v-if="notification.user"
|
||||
class="rounded-full object-cover"
|
||||
:alt="notification.user.name"
|
||||
:src="notification.user.profile_photo_url"
|
||||
>
|
||||
<GoogleIcon
|
||||
v-else
|
||||
name="tag"
|
||||
class="text-white text-2xl"
|
||||
<GoogleIcon v-else
|
||||
name="tag"
|
||||
class="text-white text-2xl"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="ml-3 w-full">
|
||||
<div class="text-sm font-medium truncate">{{ notification.data.title }}</div>
|
||||
<div v-if="notification.user" class="text-xs text-gray-400 truncate">~ {{ `${notification.user.name} ${notification.user.paternal}` }} </div>
|
||||
<div v-else class="text-xs text-gray-400 truncate">~ {{ $t('system') }} </div>
|
||||
<div
|
||||
v-text="notification.data.title"
|
||||
class="text-sm font-medium truncate"
|
||||
/>
|
||||
<div v-if="notification.user"
|
||||
v-text="`~ ${notification.user.name} ${notification.user.paternal}`"
|
||||
class="text-xs text-gray-400 truncate"
|
||||
/>
|
||||
<div v-else
|
||||
v-text="$t('system')"
|
||||
class="text-xs text-gray-400 truncate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
</template>
|
||||
@ -1,6 +1,9 @@
|
||||
<script setup>
|
||||
import useRightSidebar from '@Stores/RightSidebar'
|
||||
|
||||
/** Definidores */
|
||||
const rightSidebar = useRightSidebar()
|
||||
|
||||
/** Eventos */
|
||||
const emit = defineEmits(['open']);
|
||||
|
||||
@ -8,9 +11,6 @@ const emit = defineEmits(['open']);
|
||||
const props = defineProps({
|
||||
sidebar: Boolean
|
||||
});
|
||||
|
||||
/** Definidores */
|
||||
const rightSidebar = useRightSidebar()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
name: String
|
||||
});
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
name: String
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@ -13,7 +13,7 @@ const props = defineProps({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="py-4">
|
||||
<section class="pb-2">
|
||||
<div class="w-full overflow-hidden rounded-md shadow-lg">
|
||||
<div class="w-full overflow-x-auto">
|
||||
<table class="w-full">
|
||||
|
||||
@ -1,22 +1,18 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
/**
|
||||
* Propiedades
|
||||
*/
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
fill: Boolean,
|
||||
title: String,
|
||||
style: {
|
||||
type: String,
|
||||
default: 'rounded' // outlined, rounded, sharp
|
||||
}
|
||||
},
|
||||
title: String
|
||||
})
|
||||
|
||||
/**
|
||||
* Propiedades computadas
|
||||
*/
|
||||
/** Propiedades computadas */
|
||||
const classes = computed(() => {
|
||||
return props.fill
|
||||
? `font-google-icon-${props.style}-fill`
|
||||
@ -26,9 +22,9 @@ const classes = computed(() => {
|
||||
|
||||
<template>
|
||||
<span
|
||||
v-text="name"
|
||||
class="material-symbols cursor-pointer"
|
||||
:class="classes"
|
||||
translate="no"
|
||||
v-text="name"
|
||||
/>
|
||||
</template>
|
||||
11
src/config.js
Normal file
11
src/config.js
Normal file
@ -0,0 +1,11 @@
|
||||
import config from '../package.json'
|
||||
|
||||
const APP_COPYRIGHT = config.copyright
|
||||
const APP_NAME = import.meta.env.VITE_APP_NAME
|
||||
const APP_VERSION = config.version
|
||||
|
||||
export {
|
||||
APP_NAME,
|
||||
APP_VERSION,
|
||||
APP_COPYRIGHT
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
/* Obtiene la fecha actual en el formato deseado */
|
||||
// Obtener fecha en formato deseado
|
||||
function getDate(value = null) {
|
||||
const date = (value)
|
||||
? DateTime.fromISO(value)
|
||||
@ -9,7 +9,7 @@ function getDate(value = null) {
|
||||
return date.toLocaleString(DateTime.DATE_MED);
|
||||
}
|
||||
|
||||
/* Obtiene la horaa actual en el formato deseado */
|
||||
// Obtener hora en formato deseado
|
||||
function getTime(value = null) {
|
||||
const date = (value)
|
||||
? DateTime.fromISO(value)
|
||||
@ -18,7 +18,7 @@ function getTime(value = null) {
|
||||
return date.toLocaleString(DateTime.TIME_24_SIMPLE);
|
||||
}
|
||||
|
||||
/** Obtener fecha y hora */
|
||||
// Obtener fecha y hora
|
||||
function getDateTime(value) {
|
||||
const date = (value)
|
||||
? DateTime.fromISO(value)
|
||||
@ -27,4 +27,8 @@ function getDateTime(value) {
|
||||
return date.toLocaleString(DateTime.DATETIME_SHORT);
|
||||
}
|
||||
|
||||
export { getDate, getTime, getDateTime }
|
||||
export {
|
||||
getDate,
|
||||
getDateTime,
|
||||
getTime
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
import { ref } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
/**
|
||||
* Controla la generación de impresiones
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
import { ref } from 'vue';
|
||||
import { router } from '@inertiajs/vue3';
|
||||
import { api } from '@Services/Api.js';
|
||||
|
||||
/**
|
||||
* Controlador simple de las bandejas
|
||||
*/
|
||||
class SearcherController
|
||||
{
|
||||
route = '';
|
||||
params = {};
|
||||
query = ref('');
|
||||
|
||||
constructor(route, params) {
|
||||
|
||||
constructor({ route, model, params = {} }) {
|
||||
this.route = route;
|
||||
this.model = ref(model);
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@ -18,44 +21,64 @@ class SearcherController
|
||||
*/
|
||||
search = (q = '', params) => {
|
||||
this.query.value = q;
|
||||
router.get(this._getRoute(), {
|
||||
q,
|
||||
...params
|
||||
}, {preserveState: true});
|
||||
api.get(this._getRoute(), {
|
||||
params: {
|
||||
q: this.query.value,
|
||||
...params
|
||||
},
|
||||
onSuccess: (r) => {
|
||||
this.model.value = r.users;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Paginación simple
|
||||
*/
|
||||
withPagination = (page, params) => {
|
||||
router.get(this._getRoute(), {
|
||||
page,
|
||||
...params
|
||||
}, {preserveState: true});
|
||||
api.get(this._getRoute(), {
|
||||
params: {
|
||||
page,
|
||||
...params
|
||||
},
|
||||
onSuccess: (r) => {
|
||||
this.model.value = r.users;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Búsqueda con paginación en tablas
|
||||
* Búsqueda con Paginación en tablas
|
||||
*/
|
||||
searchWithPagination = (page, params) => {
|
||||
router.get(page, {
|
||||
q: this.query.value,
|
||||
...params
|
||||
}, {preserveState: true});
|
||||
api.get(page, {
|
||||
params: {
|
||||
q: this.query.value,
|
||||
...params
|
||||
},
|
||||
onSuccess: (r) => {
|
||||
this.model.value = r.users;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Búsqueda con páginación en bandejas
|
||||
* Búsqueda con Paginación en bandejas
|
||||
*/
|
||||
searchWithInboxPagination = (page, params) => {
|
||||
router.get(page, {
|
||||
q: this.query.value,
|
||||
...params
|
||||
}, {preserveState: true});
|
||||
api.get(page, {
|
||||
params: {
|
||||
q: this.query.value,
|
||||
...params
|
||||
},
|
||||
onSuccess: (r) => {
|
||||
this.model.value = r.users;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene la ruta segun los parametros
|
||||
* Obtiene la ruta según los parámetros
|
||||
*/
|
||||
_getRoute = () => {
|
||||
return (this.params)
|
||||
|
||||
94
src/css/app.css
Normal file
94
src/css/app.css
Normal file
@ -0,0 +1,94 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.app-bg-light {
|
||||
@apply bg-primary
|
||||
}
|
||||
|
||||
.app-bg-dark {
|
||||
@apply bg-primary-d
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply inline-flex justify-center items-center w-fit px-1.5 py-1.5 rounded-md font-medium border border-transparent text-xs text-white uppercase tracking-widest hover:opacity-90 focus:outline-none active:saturate-150 disabled:opacity-25 transition;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply bg-primary dark:bg-primary-d text-primary-t dark:text-primary-dt hover:bg-secondary dark:hover:bg-secondary-d hover:text-secondary-t dark:hover:text-secondary-dt;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-secondary dark:bg-secondary-d text-secondary-t dark:text-secondary-dt hover:bg-secondary dark:hover:bg-secondary-d hover:text-secondary-t dark:hover:text-secondary-dt;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
@apply bg-success dark:bg-success-d text-success-t dark:text-success-dt hover:bg-success dark:hover:bg-success hover:text-success-t dark:hover:text-success-dt;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
@apply bg-danger dark:bg-danger-d text-danger-t dark:text-danger-dt hover:bg-danger dark:hover:bg-danger hover:text-danger-t dark:hover:text-danger-dt;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
@apply bg-warning dark:bg-warning-d text-warning-t dark:text-warning-dt hover:bg-warning dark:hover:bg-warning hover:text-warning-t dark:hover:text-warning-dt;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
@apply flex w-fit min-h-6 px-1.5 py-1.5 rounded-md font-medium bg-primary dark:bg-primary-d text-primary-t dark:text-primary-dt hover:bg-secondary dark:hover:bg-secondary-d hover:text-secondary-t dark:hover:text-secondary-dt;
|
||||
}
|
||||
|
||||
.input-primary {
|
||||
@apply w-full p-2 border rounded-md outline-0 bg-transparent
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
@apply p-1
|
||||
}
|
||||
|
||||
.table-item {
|
||||
@apply px-2 border text-sm;
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
@apply flex justify-center items-center space-x-2;
|
||||
}
|
||||
|
||||
nav a.router-link-active {
|
||||
@apply bg-secondary/30 dark:bg-secondary-d/30 border-secondary dark:border-secondary-d
|
||||
}
|
||||
|
||||
table {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
table>thead {
|
||||
@apply bg-primary text-white;
|
||||
}
|
||||
|
||||
table>thead>tr {
|
||||
@apply text-base font-semibold tracking-wide text-left text-white bg-primary dark:bg-primary-d uppercase border-b divide-indigo-50 divide-x;
|
||||
}
|
||||
|
||||
table>thead>tr>th {
|
||||
@apply px-2 border border-white text-sm;
|
||||
}
|
||||
|
||||
.with-transition {
|
||||
@apply transition-all duration-300
|
||||
}
|
||||
|
||||
.with-color-transition {
|
||||
@apply transition-colors duration-500
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch
|
||||
*/
|
||||
.toggle-checkbox:checked {
|
||||
@apply right-0 border-slate-500
|
||||
}
|
||||
|
||||
.toggle-checkbox:checked + .toggle-label {
|
||||
@apply bg-slate-500
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import './icons.css';
|
||||
@import './notifications.css';
|
||||
@import "vue-multiselect/dist/vue-multiselect.css";
|
||||
@import './multiselect.css';
|
||||
@import './app.css';
|
||||
92
src/css/icons.css
Normal file
92
src/css/icons.css
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Outlined
|
||||
*
|
||||
* https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0
|
||||
* https://fonts.gstatic.com/s/materialsymbolsoutlined/v170/kJF1BvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oDMzByHX9rA6RzaxHMPdY43zj-jCxv3fzvRNU22ZXGJpEpjC_1v-p_4MrImHCIJIZrDCvHOej.woff2
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(./icons/google/kJF1BvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oDMzByHX9rA6RzaxHMPdY43zj-jCxv3fzvRNU22ZXGJpEpjC_1v-p_4MrImHCIJIZrDCvHOej.woff2) format('woff2');
|
||||
}
|
||||
|
||||
/**
|
||||
* Outlined fill
|
||||
*
|
||||
* https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,1,0
|
||||
* https://fonts.gstatic.com/s/materialsymbolsoutlined/v170/kJF1BvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oDMzByHX9rA6RzazHD_dY43zj-jCxv3fzvRNU22ZXGJpEpjC_1v-p_4MrImHCIJIZrDCvHOej.woff2
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Outlined Fill';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(./icons/google/kJF1BvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oDMzByHX9rA6RzazHD_dY43zj-jCxv3fzvRNU22ZXGJpEpjC_1v-p_4MrImHCIJIZrDCvHOej.woff2) format('woff2');
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounded
|
||||
*
|
||||
* https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,0,0
|
||||
* https://fonts.gstatic.com/s/materialsymbolsrounded/v168/syl0-zNym6YjUruM-QrEh7-nyTnjDwKNJ_190FjpZIvDmUSVOK7BDB_Qb9vUSzq3wzLK-P0J-V_Zs-QtQth3-jOcbTCVpeRL2w5rwZu2rIelXxc.woff2
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Rounded';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(./icons/google/syl0-zNym6YjUruM-QrEh7-nyTnjDwKNJ_190FjpZIvDmUSVOK7BDB_Qb9vUSzq3wzLK-P0J-V_Zs-QtQth3-jOcbTCVpeRL2w5rwZu2rIelXxc.woff2) format('woff2');
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounded fill
|
||||
*
|
||||
* https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,1,0
|
||||
* https://fonts.gstatic.com/s/materialsymbolsrounded/v168/syl0-zNym6YjUruM-QrEh7-nyTnjDwKNJ_190FjpZIvDmUSVOK7BDJ_vb9vUSzq3wzLK-P0J-V_Zs-QtQth3-jOcbTCVpeRL2w5rwZu2rIelXxc.woff2
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Rounded Fill';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(./icons/google/syl0-zNym6YjUruM-QrEh7-nyTnjDwKNJ_190FjpZIvDmUSVOK7BDJ_vb9vUSzq3wzLK-P0J-V_Zs-QtQth3-jOcbTCVpeRL2w5rwZu2rIelXxc.woff2) format('woff2');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sharp
|
||||
*
|
||||
* https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:opsz,wght,FILL,GRAD@24,400,0,0
|
||||
* https://fonts.gstatic.com/s/materialsymbolssharp/v166/gNNBW2J8Roq16WD5tFNRaeLQk6-SHQ_R00k4c2_whPnoY9ruReaU4bHmz74m0ZkGH-VBYe1x0TV6x4yFH8F-H5OdzEL3sVTgJtfbYxOLojCL.woff2
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Sharp';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(./icons/google/gNNBW2J8Roq16WD5tFNRaeLQk6-SHQ_R00k4c2_whPnoY9ruReaU4bHmz74m0ZkGH-VBYe1x0TV6x4yFH8F-H5OdzEL3sVTgJtfbYxOLojCL.woff2) format('woff2');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sharp fill
|
||||
*
|
||||
* https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:opsz,wght,FILL,GRAD@24,400,1,0
|
||||
* https://fonts.gstatic.com/s/materialsymbolssharp/v166/gNNBW2J8Roq16WD5tFNRaeLQk6-SHQ_R00k4c2_whPnoY9ruReYU3rHmz74m0ZkGH-VBYe1x0TV6x4yFH8F-H5OdzEL3sVTgJtfbYxOLojCL.woff2
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Sharp Fill';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(./icons/google/gNNBW2J8Roq16WD5tFNRaeLQk6-SHQ_R00k4c2_whPnoY9ruReYU3rHmz74m0ZkGH-VBYe1x0TV6x4yFH8F-H5OdzEL3sVTgJtfbYxOLojCL.woff2) format('woff2');
|
||||
}
|
||||
|
||||
.material-symbols{
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 20spx;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-webkit-font-feature-settings: 'liga';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
57
src/css/multiselect.css
Normal file
57
src/css/multiselect.css
Normal file
@ -0,0 +1,57 @@
|
||||
.multiselect {
|
||||
@apply dark:text-white min-h-8;
|
||||
}
|
||||
|
||||
.multiselect__input,
|
||||
.multiselect__single {
|
||||
@apply bg-white dark:bg-primary dark:text-white
|
||||
}
|
||||
|
||||
.multiselect__input::placeholder {
|
||||
@apply text-gray-300;
|
||||
}
|
||||
|
||||
.multiselect__tag {
|
||||
@apply dark:bg-green-800;
|
||||
}
|
||||
|
||||
.multiselect__tag-icon::after {
|
||||
@apply text-gray-300;
|
||||
}
|
||||
|
||||
.multiselect__tag-icon:focus::after,
|
||||
.multiselect__tag-icon:hover::after {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
.multiselect__tags {
|
||||
@apply dark:bg-primary min-h-8 border-0 border-b border-b-slate-400 pt-1 with-color-transition;
|
||||
}
|
||||
|
||||
.multiselect__option--highlight {
|
||||
@apply dark:bg-green-900 dark:text-white outline-0;
|
||||
}
|
||||
|
||||
.multiselect__option--highlight::after {
|
||||
@apply dark:bg-green-800 dark:text-white;
|
||||
}
|
||||
|
||||
.multiselect__option--selected.multiselect__option--highlight {
|
||||
@apply bg-red-500 dark:bg-red-600 text-white;
|
||||
}
|
||||
|
||||
.multiselect__option--selected.multiselect__option--highlight::after {
|
||||
@apply bg-red-400 dark:bg-red-500 text-white;
|
||||
}
|
||||
|
||||
.multiselect__content-wrapper {
|
||||
@apply bg-gray-100 dark:bg-primary;
|
||||
}
|
||||
|
||||
|
||||
.multiselect--disabled,
|
||||
.multiselect--disabled .multiselect__select,
|
||||
.multiselect--disabled .multiselect__current,
|
||||
.multiselect__option--disabled.multiselect__option--highlight{
|
||||
background: none;
|
||||
}
|
||||
234
src/css/notifications.css
Normal file
234
src/css/notifications.css
Normal file
@ -0,0 +1,234 @@
|
||||
.toast-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
.toast-message {
|
||||
-ms-word-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.toast-message a,
|
||||
.toast-message label {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.toast-message a:hover {
|
||||
color: #CCCCCC;
|
||||
text-decoration: none;
|
||||
}
|
||||
.toast-close-button {
|
||||
position: relative;
|
||||
right: -0.3em;
|
||||
top: -0.3em;
|
||||
float: right;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
-webkit-text-shadow: 0 1px 0 #ffffff;
|
||||
text-shadow: 0 1px 0 #ffffff;
|
||||
opacity: 0.8;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
|
||||
filter: alpha(opacity=80);
|
||||
line-height: 1;
|
||||
}
|
||||
.toast-close-button:hover,
|
||||
.toast-close-button:focus {
|
||||
color: #000000;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
opacity: 0.4;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
|
||||
filter: alpha(opacity=40);
|
||||
}
|
||||
.rtl .toast-close-button {
|
||||
left: -0.3em;
|
||||
float: left;
|
||||
right: 0.3em;
|
||||
}
|
||||
/*Additional properties for button version
|
||||
iOS requires the button element instead of an anchor tag.
|
||||
If you want the anchor version, it requires `href="#"`.*/
|
||||
button.toast-close-button {
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
.toast-top-center {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.toast-bottom-center {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.toast-top-full-width {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.toast-bottom-full-width {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.toast-top-left {
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
}
|
||||
.toast-top-right {
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
.toast-bottom-right {
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
}
|
||||
.toast-bottom-left {
|
||||
bottom: 12px;
|
||||
left: 12px;
|
||||
}
|
||||
#toast-container {
|
||||
position: fixed;
|
||||
z-index: 999999;
|
||||
pointer-events: none;
|
||||
/*overrides*/
|
||||
}
|
||||
#toast-container * {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#toast-container > div {
|
||||
position: relative;
|
||||
pointer-events: auto;
|
||||
overflow: hidden;
|
||||
margin: 0 0 6px;
|
||||
padding: 15px 15px 15px 50px;
|
||||
width: 300px;
|
||||
-moz-border-radius: 3px 3px 3px 3px;
|
||||
-webkit-border-radius: 3px 3px 3px 3px;
|
||||
border-radius: 3px 3px 3px 3px;
|
||||
background-position: 15px center;
|
||||
background-repeat: no-repeat;
|
||||
-moz-box-shadow: 0 0 12px #999999;
|
||||
-webkit-box-shadow: 0 0 12px #999999;
|
||||
box-shadow: 0 0 12px #999999;
|
||||
color: #FFFFFF;
|
||||
opacity: 0.8;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
|
||||
filter: alpha(opacity=80);
|
||||
}
|
||||
#toast-container > div.rtl {
|
||||
direction: rtl;
|
||||
padding: 15px 50px 15px 15px;
|
||||
background-position: right 15px center;
|
||||
}
|
||||
#toast-container > div:hover {
|
||||
-moz-box-shadow: 0 0 12px #000000;
|
||||
-webkit-box-shadow: 0 0 12px #000000;
|
||||
box-shadow: 0 0 12px #000000;
|
||||
opacity: 1;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
|
||||
filter: alpha(opacity=100);
|
||||
cursor: pointer;
|
||||
}
|
||||
#toast-container > .toast-info {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important;
|
||||
}
|
||||
#toast-container > .toast-error {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important;
|
||||
}
|
||||
#toast-container > .toast-success {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important;
|
||||
}
|
||||
#toast-container > .toast-warning {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important;
|
||||
}
|
||||
#toast-container.toast-top-center > div,
|
||||
#toast-container.toast-bottom-center > div {
|
||||
width: 300px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
#toast-container.toast-top-full-width > div,
|
||||
#toast-container.toast-bottom-full-width > div {
|
||||
width: 96%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.toast {
|
||||
background-color: #030303;
|
||||
}
|
||||
|
||||
.toast-success {
|
||||
@apply bg-success dark:bg-success-d;
|
||||
}
|
||||
|
||||
.toast-error {
|
||||
@apply bg-danger dark:bg-danger-d;
|
||||
}
|
||||
|
||||
.toast-info {
|
||||
@apply bg-primary-info dark:bg-primary-info-d;
|
||||
}
|
||||
|
||||
.toast-warning {
|
||||
@apply bg-warning dark:bg-warning-d;
|
||||
}
|
||||
|
||||
.toast-progress {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 4px;
|
||||
background-color: #000000;
|
||||
opacity: 0.4;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
|
||||
filter: alpha(opacity=40);
|
||||
}
|
||||
/*Responsive Design*/
|
||||
@media all and (max-width: 240px) {
|
||||
#toast-container > div {
|
||||
padding: 8px 8px 8px 50px;
|
||||
width: 11em;
|
||||
}
|
||||
#toast-container > div.rtl {
|
||||
padding: 8px 50px 8px 8px;
|
||||
}
|
||||
#toast-container .toast-close-button {
|
||||
right: -0.2em;
|
||||
top: -0.2em;
|
||||
}
|
||||
#toast-container .rtl .toast-close-button {
|
||||
left: -0.2em;
|
||||
right: 0.2em;
|
||||
}
|
||||
}
|
||||
@media all and (min-width: 241px) and (max-width: 480px) {
|
||||
#toast-container > div {
|
||||
padding: 8px 8px 8px 50px;
|
||||
width: 18em;
|
||||
}
|
||||
#toast-container > div.rtl {
|
||||
padding: 8px 50px 8px 8px;
|
||||
}
|
||||
#toast-container .toast-close-button {
|
||||
right: -0.2em;
|
||||
top: -0.2em;
|
||||
}
|
||||
#toast-container .rtl .toast-close-button {
|
||||
left: -0.2em;
|
||||
right: 0.2em;
|
||||
}
|
||||
}
|
||||
@media all and (min-width: 481px) and (max-width: 768px) {
|
||||
#toast-container > div {
|
||||
padding: 15px 15px 15px 50px;
|
||||
width: 25em;
|
||||
}
|
||||
#toast-container > div.rtl {
|
||||
padding: 15px 50px 15px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
52
src/index.js
Normal file
52
src/index.js
Normal file
@ -0,0 +1,52 @@
|
||||
import './css/base.css'
|
||||
|
||||
import axios from 'axios';
|
||||
import { createPinia } from 'pinia'
|
||||
import { createApp } from 'vue'
|
||||
import { useRoute, ZiggyVue } from 'ziggy-js';
|
||||
import { i18n, lang } from '@/lang/i18n.js';
|
||||
import router from '@Router/Index'
|
||||
import Notify from '@Plugins/Notify'
|
||||
import TailwindScreen from '@Plugins/TailwindScreen'
|
||||
import { pagePlugin } from '@Services/Page';
|
||||
|
||||
import App from '@Layouts/AppLayout.vue'
|
||||
import { view } from '@Services/Page';
|
||||
|
||||
// Configurar axios
|
||||
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
// Crear instancias globales
|
||||
window.axios = axios;
|
||||
window.Lang = lang;
|
||||
window.Notify = new Notify();
|
||||
window.TwScreen = new TailwindScreen();
|
||||
|
||||
async function boot() {
|
||||
try {
|
||||
const { data } = await axios.get(import.meta.env.VITE_API_URL + '/api/routes');
|
||||
|
||||
// Iniciar rutas
|
||||
window.Ziggy = data;
|
||||
window.route = useRoute();
|
||||
window.view = view;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert('Failed to load routes');
|
||||
}
|
||||
|
||||
if(import.meta.env.VITE_REVERB_ACTIVE === 'true') {
|
||||
await import('@Services/Broadcast')
|
||||
}
|
||||
|
||||
createApp(App)
|
||||
.use(createPinia())
|
||||
.use(i18n)
|
||||
.use(pagePlugin)
|
||||
.use(router)
|
||||
.use(ZiggyVue)
|
||||
.mount('#app');
|
||||
}
|
||||
|
||||
// Iniciar aplicación
|
||||
boot();
|
||||
@ -300,6 +300,9 @@ export default {
|
||||
search:'Buscar',
|
||||
selected: 'Seleccionado',
|
||||
select: 'Seleccionar',
|
||||
session: {
|
||||
closed: 'Sesión cerrada',
|
||||
},
|
||||
setting: 'Configuración',
|
||||
settings: {
|
||||
assistances: {
|
||||
@ -393,6 +396,7 @@ export default {
|
||||
title:'Usuarios',
|
||||
},
|
||||
version:'Versión',
|
||||
welcome: '<b>Bienvenido</b> {name}.',
|
||||
workstation: 'Puesto de trabajo',
|
||||
workstations: {
|
||||
create: {
|
||||
|
||||
@ -18,6 +18,11 @@ const i18n = createI18n({
|
||||
messages
|
||||
});
|
||||
|
||||
const lang = (text) => i18n.global.t(text);
|
||||
function lang(text) {
|
||||
return i18n.global.t(text);
|
||||
}
|
||||
|
||||
export {i18n, lang};
|
||||
export {
|
||||
i18n,
|
||||
lang
|
||||
};
|
||||
@ -1,17 +1,11 @@
|
||||
<script setup>
|
||||
import { hasPermission } from '@Plugins/RolePermission.js';
|
||||
|
||||
import Layout from '@Holos/Layout/AppLayout.vue';
|
||||
import Layout from '@Holos/Layout/App.vue';
|
||||
import Link from '@Holos/Skeleton/Sidebar/Link.vue';
|
||||
import Section from '@Holos/Skeleton/Sidebar/Section.vue';
|
||||
|
||||
/** Propiedades */
|
||||
defineProps({
|
||||
title: String,
|
||||
titlePage: {
|
||||
default: true,
|
||||
type: Boolean
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
@ -19,40 +13,31 @@ defineProps({
|
||||
<template>
|
||||
<Layout
|
||||
:title="title"
|
||||
:titlePage="titlePage"
|
||||
>
|
||||
|
||||
<template #leftSidebar>
|
||||
<Section name="Principal">
|
||||
<Link
|
||||
icon="monitoring"
|
||||
name="dashboard"
|
||||
to="dashboard.index"
|
||||
to="index"
|
||||
/>
|
||||
</Section>
|
||||
<Section :name="$t('account.title')">
|
||||
<Link
|
||||
icon="manage_accounts"
|
||||
name="profile"
|
||||
<Link
|
||||
icon="person"
|
||||
name="profile"
|
||||
to="profile.show"
|
||||
/>
|
||||
<Link
|
||||
icon="notifications"
|
||||
:name="$t('notifications.title')"
|
||||
to="notifications.index"
|
||||
/>
|
||||
</Section>
|
||||
<Section :name="$t('admin.title')">
|
||||
<Link
|
||||
v-if="hasPermission('users.index')"
|
||||
<Link
|
||||
icon="people"
|
||||
name="users.title"
|
||||
name="users.title"
|
||||
to="admin.users.index"
|
||||
/>
|
||||
</Section>
|
||||
</template>
|
||||
|
||||
<!-- Contenido -->
|
||||
<slot />
|
||||
<RouterView />
|
||||
<!-- Fin contenido -->
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
@ -1,22 +1,20 @@
|
||||
<script setup>
|
||||
import { Link, useForm } from '@inertiajs/vue3';
|
||||
import { goTo, transl } from './Module';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { api, useForm } from '@Services/Api';
|
||||
import { viewTo } from './Module';
|
||||
|
||||
import IconButton from '@Holos/Button/Icon.vue'
|
||||
import Input from '@Holos/Form/Input.vue';
|
||||
import Selectable from '@Holos/Form/Selectable.vue';
|
||||
import PageHeader from '@Holos/PageHeader.vue';
|
||||
import DashboardLayout from '@Layouts/AppLayout.vue';
|
||||
import Form from './Form.vue'
|
||||
|
||||
/** Definidores */
|
||||
const router = useRouter();
|
||||
|
||||
/** Propiedades */
|
||||
|
||||
defineProps({
|
||||
roles: Object
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
_id: null,
|
||||
name: '',
|
||||
paternal: '',
|
||||
maternal: '',
|
||||
@ -26,30 +24,41 @@ const form = useForm({
|
||||
roles: []
|
||||
});
|
||||
|
||||
const roles = ref([]);
|
||||
|
||||
/** Métodos */
|
||||
function submit() {
|
||||
form.transform(data => ({
|
||||
...data,
|
||||
roles: data.roles.map(role => role.id)
|
||||
})).post(route(goTo('store')), {
|
||||
onSuccess: () => Notify.success(lang('register.create.onSuccess')),
|
||||
onError: () => Notify.error(lang('register.create.onError')),
|
||||
onFinish: () => form.reset('password')
|
||||
})).post(apiTo('store'), {
|
||||
onSuccess: () => {
|
||||
Notify.success(Lang('register.create.onSuccess'))
|
||||
router.push(viewTo({ name: 'index' }));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** Ciclos */
|
||||
onMounted(() => {
|
||||
api.get(route('system.roles'), {
|
||||
onSuccess: (r) => {
|
||||
roles.value = r.roles;
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout :title="transl('create.title')">
|
||||
<PageHeader>
|
||||
<Link :href="route(goTo('index'))">
|
||||
<RouterLink :to="viewTo({ name: 'index' })">
|
||||
<IconButton
|
||||
class="text-white"
|
||||
icon="arrow_back"
|
||||
:title="$t('return')"
|
||||
filled
|
||||
/>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</PageHeader>
|
||||
<Form
|
||||
action="create"
|
||||
@ -72,5 +81,4 @@ function submit() {
|
||||
multiple
|
||||
/>
|
||||
</Form>
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
|
||||
@ -1,52 +1,58 @@
|
||||
<script setup>
|
||||
import { Link, useForm } from '@inertiajs/vue3';
|
||||
import { goTo, transl } from './Module';
|
||||
import { onMounted } from 'vue';
|
||||
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
||||
import { api, useForm } from '@Services/Api';
|
||||
import { viewTo, apiTo } from './Module';
|
||||
|
||||
import IconButton from '@Holos/Button/Icon.vue'
|
||||
import PageHeader from '@Holos/PageHeader.vue';
|
||||
import Layout from '@/Layouts/AppLayout.vue';
|
||||
import Form from './Form.vue'
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
model: Object,
|
||||
});
|
||||
/** Definiciones */
|
||||
const vroute = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
/** Propiedades */
|
||||
const form = useForm({
|
||||
name: props.model.name,
|
||||
paternal: props.model.paternal,
|
||||
maternal: props.model.maternal,
|
||||
email: props.model.email,
|
||||
phone: props.model.phone,
|
||||
id: null,
|
||||
name: '',
|
||||
paternal: '',
|
||||
maternal: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
});
|
||||
|
||||
/** Métodos */
|
||||
function submit() {
|
||||
form.put(route(goTo('update'), {user:props.model.id}), {
|
||||
onSuccess: () => Notify.success(lang('register.edit.onSuccess')),
|
||||
onError: () => Notify.error(lang('register.edit.onError')),
|
||||
onFinish: () => form.reset('password')
|
||||
form.put(apiTo('update', { user: form.id }), {
|
||||
onSuccess: () => {
|
||||
Notify.success(Lang('register.edit.onSuccess'))
|
||||
router.push(viewTo({ name: 'index' }));
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
api.get(apiTo('show', { user: vroute.params.id }), {
|
||||
onSuccess: (r) => form.fill(r.user)
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout :title="transl('edit.title')">
|
||||
<PageHeader>
|
||||
<Link :href="route(goTo('index'))">
|
||||
<RouterLink :to="viewTo({ name: 'index' })">
|
||||
<IconButton
|
||||
class="text-white"
|
||||
icon="arrow_back"
|
||||
:title="$t('return')"
|
||||
filled
|
||||
/>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</PageHeader>
|
||||
<Form
|
||||
action="update"
|
||||
:form="form"
|
||||
@submit="submit"
|
||||
/>
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
@ -68,9 +68,9 @@ function submit() {
|
||||
<slot />
|
||||
<div class="col-span-1 md:col-span-2 lg:col-span-3 xl:col-span-4 flex flex-col items-center justify-end space-y-4 mt-4">
|
||||
<PrimaryButton
|
||||
:class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing"
|
||||
v-text="$t(action)"
|
||||
:class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -1,40 +1,53 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { transl, can, goTo } from './Module'
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { can, apiTo, viewTo } from './Module'
|
||||
import { api } from '@Services/Api';
|
||||
|
||||
import ModalController from '@/Controllers/ModalController.js';
|
||||
import SearcherController from '@/Controllers/SearcherController.js';
|
||||
import ModalController from '@Controllers/ModalController.js';
|
||||
import SearcherController from '@Controllers/SearcherController.js';
|
||||
|
||||
import IconButton from '@Holos/Button/Icon.vue'
|
||||
import DestroyView from '@Holos/Modal/Template/Destroy.vue';
|
||||
import SearcherHead from '@Holos/Searcher.vue';
|
||||
import Table from '@Holos/Table.vue';
|
||||
import DashboardLayout from '@Layouts/AppLayout.vue';
|
||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||
import ShowView from './Modals/Show.vue';
|
||||
|
||||
/** Eventos */
|
||||
const props = defineProps({
|
||||
models: Object
|
||||
});
|
||||
|
||||
/** Controladores */
|
||||
const Modal = new ModalController();
|
||||
const Searcher = new SearcherController(goTo('index'));
|
||||
|
||||
/** Propiedades */
|
||||
const destroyModal = ref(Modal.destroyModal);
|
||||
const showModal = ref(Modal.showModal);
|
||||
const modelModal = ref(Modal.modelModal);
|
||||
|
||||
const models = ref([]);
|
||||
|
||||
const Searcher = new SearcherController({
|
||||
route: 'users.index',
|
||||
model: models
|
||||
});
|
||||
|
||||
/** Métodos */
|
||||
function load() {
|
||||
api.get(apiTo('index'), {
|
||||
onSuccess: (r) => models.value = r.users
|
||||
});
|
||||
}
|
||||
|
||||
/** Ciclos */
|
||||
onMounted(() => load());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout :title="transl('system')">
|
||||
<SearcherHead @search="Searcher.search">
|
||||
<Link
|
||||
v-if="can('create')"
|
||||
:href="route(goTo('create'))"
|
||||
<div>
|
||||
<SearcherHead
|
||||
:title="$t('users.title')"
|
||||
@search="Searcher.search"
|
||||
>
|
||||
<RouterLink
|
||||
v-if="can('create')"
|
||||
:to="viewTo({ name: 'create' })"
|
||||
>
|
||||
<IconButton
|
||||
class="text-white"
|
||||
@ -42,7 +55,7 @@ const modelModal = ref(Modal.modelModal);
|
||||
:title="$t('crud.create')"
|
||||
filled
|
||||
/>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</SearcherHead>
|
||||
<div class="pt-2 w-full">
|
||||
<Table
|
||||
@ -92,10 +105,10 @@ const modelModal = ref(Modal.modelModal);
|
||||
@click="Modal.switchShowModal(model)"
|
||||
outline
|
||||
/>
|
||||
<Link
|
||||
<RouterLink
|
||||
v-if="can('edit')"
|
||||
class="h-fit"
|
||||
:href="route(goTo('edit'), model.id)"
|
||||
:to="viewTo({ name: 'edit', params: { id: model.id } })"
|
||||
>
|
||||
<GoogleIcon
|
||||
class="btn-icon"
|
||||
@ -103,7 +116,7 @@ const modelModal = ref(Modal.modelModal);
|
||||
:title="$t('crud.edit')"
|
||||
outline
|
||||
/>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
<GoogleIcon
|
||||
v-if="can('destroy')"
|
||||
class="btn-icon"
|
||||
@ -112,17 +125,17 @@ const modelModal = ref(Modal.modelModal);
|
||||
@click="Modal.switchDestroyModal(model)"
|
||||
outline
|
||||
/>
|
||||
<Link
|
||||
<RouterLink
|
||||
v-if="can('settings')"
|
||||
class="h-fit"
|
||||
:href="route('admin.users.settings', model.id)"
|
||||
:to="viewTo({ name: 'settings', params: { id: model.id } })"
|
||||
>
|
||||
<GoogleIcon
|
||||
class="btn-icon"
|
||||
name="settings"
|
||||
:title="$t('setting')"
|
||||
/>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -151,9 +164,10 @@ const modelModal = ref(Modal.modelModal);
|
||||
v-if="can('destroy')"
|
||||
:model="modelModal"
|
||||
:show="destroyModal"
|
||||
:to="(user) => route(goTo('destroy'), {user})"
|
||||
:to="(user) => apiTo('destroy', { user })"
|
||||
@close="Modal.switchDestroyModal"
|
||||
@update="load"
|
||||
/>
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
import { lang } from '@/Lang/i18n';
|
||||
import { lang } from '@Lang/i18n';
|
||||
import { hasPermission } from '@Plugins/RolePermission.js';
|
||||
|
||||
// Obtener ruta
|
||||
const goTo = (route) => `admin.users.${route}`
|
||||
// Ruta API
|
||||
const apiTo = (name, params = {}) => route(`users.${name}`, params)
|
||||
|
||||
// Ruta visual
|
||||
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `admin.users.${name}`, params, query })
|
||||
|
||||
// Obtener traducción del componente
|
||||
const transl = (str) => lang(`users.${str}`)
|
||||
|
||||
// Determina si un usuario puede hacer algo no en base a los permisos
|
||||
const can = (permission) => hasPermission(`users.${permission}`)
|
||||
|
||||
export {
|
||||
can,
|
||||
goTo,
|
||||
viewTo,
|
||||
apiTo,
|
||||
transl
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { goTo, transl } from './Module';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { api, useForm } from '@Services/Api';
|
||||
import { transl } from './Module';
|
||||
|
||||
import PrimaryButton from '@Holos/Button/Primary.vue';
|
||||
import FormSection from '@Holos/FormSection.vue';
|
||||
@ -9,25 +9,38 @@ import Selectable from '@Holos/Form/Selectable.vue';
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
role: Object,
|
||||
roles: Object,
|
||||
user: Object
|
||||
userId: String
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
roles: props.role
|
||||
roles: []
|
||||
});
|
||||
|
||||
const roles = ref([]);
|
||||
|
||||
/** Métodos */
|
||||
function updateProfileInformation() {
|
||||
form.transform(data => ({
|
||||
roles: data.roles.map(role => role.id)
|
||||
})).post(route(goTo('sync-roles'), {user:props.user.id}), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => Notify.success(lang('roles.edit.onSuccess')),
|
||||
onError: () => Notify.error(lang('roles.edit.onError'))
|
||||
})).put(apiTo('roles', { user: props.userId }), {
|
||||
onSuccess: () => Notify.success(Lang('roles.edit.onSuccess')),
|
||||
onError: () => Notify.error(Lang('roles.edit.onError'))
|
||||
});
|
||||
};
|
||||
|
||||
/** Ciclos */
|
||||
onMounted(() => {
|
||||
api.get(route('system.roles'), {
|
||||
onSuccess: (r) => roles.value = r.roles
|
||||
});
|
||||
|
||||
api.get(apiTo('roles', { user: props.userId }), {
|
||||
onSuccess: (r) => {
|
||||
console.log(r);
|
||||
form.roles = r.roles
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -43,7 +56,7 @@ function updateProfileInformation() {
|
||||
<Selectable
|
||||
v-model="form.roles"
|
||||
label="description"
|
||||
title="Roles"
|
||||
title="roles.title"
|
||||
:options="roles"
|
||||
multiple
|
||||
/>
|
||||
|
||||
@ -1,58 +1,59 @@
|
||||
<script setup>
|
||||
import { goTo, transl } from './Module';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { RouterLink, useRoute } from 'vue-router';
|
||||
import { api } from '@Services/Api';
|
||||
import { viewTo, to } from './Module';
|
||||
|
||||
import IconButton from '@Holos/Button/Icon.vue';
|
||||
import PageHeader from '@Holos/PageHeader.vue';
|
||||
import SectionBorder from '@Holos/SectionBorder.vue';
|
||||
import DashboardLayout from '@Layouts/AppLayout.vue';
|
||||
import Roles from './Roles.vue';
|
||||
import UpdatePassword from './UpdatePassword.vue';
|
||||
|
||||
/**
|
||||
* Propiedades
|
||||
*/
|
||||
defineProps({
|
||||
role: Object,
|
||||
roles: Object,
|
||||
user: Object
|
||||
});
|
||||
/** Definiciones */
|
||||
const vroute = useRoute();
|
||||
|
||||
/** Propiedades */
|
||||
const user = ref();
|
||||
|
||||
/** Ciclos */
|
||||
onMounted(() => {
|
||||
api.get(apiTo('show', { user: vroute.params.id }), {
|
||||
onSuccess: (r) => user.value = r.user
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout :title="transl('settings')">
|
||||
<PageHeader>
|
||||
<Link :href="route(goTo('index'))">
|
||||
<RouterLink :to="viewTo({ name: 'index' })">
|
||||
<IconButton
|
||||
class="text-white"
|
||||
icon="arrow_back"
|
||||
:title="$t('return')"
|
||||
outline
|
||||
/>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</PageHeader>
|
||||
<div class="flex w-full pt-2">
|
||||
<div class="w-full text-center p-2 bg-primary dark:bg-primary-d border-b rounded-lg">
|
||||
<p class="pt-2 text-lg font-bold text-gray-50">
|
||||
{{ user.name }}
|
||||
{{ user?.name }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-100">
|
||||
{{ user.full_last_name }}
|
||||
{{ user?.full_last_name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full mt-12 space-y-4">
|
||||
<UpdatePassword
|
||||
:user="user"
|
||||
:userId="vroute.params.id"
|
||||
/>
|
||||
<SectionBorder />
|
||||
<Roles
|
||||
:role="role"
|
||||
:roles="roles"
|
||||
:user="user"
|
||||
:userId="vroute.params.id"
|
||||
/>
|
||||
<SectionBorder />
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
|
||||
@ -1,30 +1,29 @@
|
||||
<script setup>
|
||||
import { goTo, transl } from './Module';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { apiTo, transl } from './Module';
|
||||
import { useForm } from '@Services/Api';
|
||||
|
||||
import PrimaryButton from '@Holos/Button/Primary.vue';
|
||||
import Input from '@Holos/Form/Input.vue';
|
||||
import FormSection from '@Holos/FormSection.vue';
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
user: Object
|
||||
userId: String
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
_method: 'POST',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
});
|
||||
|
||||
/** Métodos */
|
||||
const updateProfileInformation = () => {
|
||||
form.post(route(goTo('password'), props.user.id), {
|
||||
errorBag: 'updateProfileInformation',
|
||||
preserveScroll: true,
|
||||
form.put(apiTo('password', { user: props.userId }), {
|
||||
onSuccess: () => {
|
||||
Notify.success(lang('account.password.updated'));
|
||||
Notify.success(Lang('account.password.updated'));
|
||||
form.reset();
|
||||
},
|
||||
onError: () => Notify.error(lang('updateFail'))
|
||||
onError: () => Notify.error(Lang('updateFail'))
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@ -40,19 +39,19 @@ const updateProfileInformation = () => {
|
||||
<template #form>
|
||||
<div class="col-span-6 sm:col-span-4 space-y-4">
|
||||
<Input
|
||||
v-model="form.password"
|
||||
id="password"
|
||||
title="account.password.new"
|
||||
type="password"
|
||||
v-model="form.password"
|
||||
:onError="form.errors.password"
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
v-model="form.password_confirmation"
|
||||
icon="password"
|
||||
id="passwordConfirmation"
|
||||
type="password"
|
||||
v-model="form.password_confirmation"
|
||||
:onError="form.errors.password_confirmation"
|
||||
required
|
||||
/>
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
<script setup>
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
|
||||
import { useForm } from '@Services/Api';
|
||||
import { useRouter } from 'vue-router';
|
||||
import Input from '@Holos/Form/InputWithIcon.vue'
|
||||
import PrimaryButton from '@Holos/Button/Primary.vue'
|
||||
import Layout from '@Holos/Layout/AuthLayout.vue'
|
||||
|
||||
/** Definidores */
|
||||
const router = useRouter()
|
||||
|
||||
/** Propiedades */
|
||||
defineProps({
|
||||
status: String,
|
||||
});
|
||||
@ -13,41 +16,46 @@ const form = useForm({
|
||||
email: '',
|
||||
});
|
||||
|
||||
/** Métodos */
|
||||
const submit = () => {
|
||||
form.post(route('password.email'));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout :title="$t('auth.forgotPassword.title')">
|
||||
<div class="mb-4 text-sm text-justify">
|
||||
{{ $t('auth.forgotPassword.description') }}
|
||||
<div class="mb-4 text-sm text-justify">
|
||||
{{ $t('auth.forgotPassword.description') }}
|
||||
</div>
|
||||
<div v-if="status" class="mb-4 font-medium text-sm text-green-600">
|
||||
{{ status }}
|
||||
</div>
|
||||
<form @submit.prevent="submit">
|
||||
<Input
|
||||
icon="mail"
|
||||
id="email"
|
||||
type="email"
|
||||
v-model="form.email"
|
||||
:onError="form.errors.email"
|
||||
:placeholder="$t('email.title')"
|
||||
/>
|
||||
<div class="flex flex-col gap-2 items-center justify-end mt-4">
|
||||
<PrimaryButton
|
||||
class="!w-full"
|
||||
:class="{ 'opacity-25': form.processing }"
|
||||
type="submit"
|
||||
:disabled="form.processing"
|
||||
>
|
||||
{{ $t('auth.forgotPassword.sendLink') }}
|
||||
</PrimaryButton>
|
||||
<PrimaryButton
|
||||
class="!w-full"
|
||||
type="button"
|
||||
:class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing"
|
||||
@click="router.push($view({ name: 'index' }))"
|
||||
>
|
||||
{{ $t('auth.login') }}
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
|
||||
<div v-if="status" class="mb-4 font-medium text-sm text-green-600">
|
||||
{{ status }}
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="submit">
|
||||
<Input
|
||||
icon="mail"
|
||||
id="email"
|
||||
type="email"
|
||||
v-model="form.email"
|
||||
:onError="form.errors.email"
|
||||
:placeholder="$t('email.title')"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<PrimaryButton
|
||||
class="!w-full"
|
||||
:class="{ 'opacity-25': form.processing }"
|
||||
type="submit"
|
||||
:disabled="form.processing"
|
||||
>
|
||||
{{ $t('auth.forgotPassword.sendLink') }}
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</form>
|
||||
</Layout>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
<script setup>
|
||||
import { Link, useForm } from '@inertiajs/vue3';
|
||||
import { onMounted } from 'vue';
|
||||
import { defineApiToken, defineCsrfToken, hasToken, useForm } from '@Services/Api.js'
|
||||
import { defineUser } from '@Services/Page';
|
||||
|
||||
import PrimaryButton from '@Holos/Button/Primary.vue'
|
||||
import Input from '@Holos/Form/InputWithIcon.vue'
|
||||
import Layout from '@Holos/Layout/AuthLayout.vue'
|
||||
|
||||
/** Propiedades */
|
||||
defineProps({
|
||||
@ -13,52 +14,58 @@ defineProps({
|
||||
|
||||
const form = useForm({
|
||||
email: '',
|
||||
password: '',
|
||||
remember: false,
|
||||
password: ''
|
||||
});
|
||||
|
||||
/** Métodos */
|
||||
const login = () => {
|
||||
form.transform(data => ({
|
||||
...data,
|
||||
remember: form.remember ? 'on' : '',
|
||||
})).post(route('login'), {
|
||||
onFinish: () => form.reset('password'),
|
||||
form.post(route('auth.login'), {
|
||||
onSuccess: (res) => {
|
||||
defineApiToken(res.token)
|
||||
defineUser(res.user)
|
||||
defineCsrfToken(res.csrf)
|
||||
|
||||
location.replace('/')
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** Ciclos */
|
||||
onMounted(() => {
|
||||
if (hasToken()) {
|
||||
location.replace('/')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout :title="$t('auth.login')">
|
||||
<form @submit.prevent="login">
|
||||
<Input
|
||||
icon="mail"
|
||||
id="email"
|
||||
type="email"
|
||||
v-model="form.email"
|
||||
:onError="form.errors.email"
|
||||
:placeholder="$t('email.title')"
|
||||
/>
|
||||
<Input
|
||||
v-model="form.password"
|
||||
icon="password"
|
||||
id="password"
|
||||
type="password"
|
||||
:onError="form.errors.password"
|
||||
:placeholder="$t('password')"
|
||||
/>
|
||||
<PrimaryButton class="!w-full">
|
||||
{{ $t('auth.login') }}
|
||||
</PrimaryButton>
|
||||
<div class="flex justify-end mt-4">
|
||||
<Link
|
||||
v-if="canResetPassword"
|
||||
class="text-sm ml-2 hover:text-blue-200 cursor-pointer hover:-translate-y-1 duration-500 transition-all"
|
||||
:href="route('password.request')"
|
||||
>
|
||||
{{ $t('auth.forgotPassword.ask') }}
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</Layout>
|
||||
<form @submit.prevent="login">
|
||||
<Input
|
||||
icon="mail"
|
||||
id="email"
|
||||
type="email"
|
||||
v-model="form.email"
|
||||
:onError="form.errors.email"
|
||||
:placeholder="$t('email.title')"
|
||||
/>
|
||||
<Input
|
||||
v-model="form.password"
|
||||
icon="password"
|
||||
id="password"
|
||||
type="password"
|
||||
:onError="form.errors.password"
|
||||
:placeholder="$t('password')"
|
||||
/>
|
||||
<PrimaryButton class="!w-full">
|
||||
{{ $t('auth.login') }}
|
||||
</PrimaryButton>
|
||||
<div class="flex justify-end mt-4">
|
||||
<RouterLink
|
||||
class="text-sm ml-2 hover:text-blue-200 cursor-pointer hover:-translate-y-1 duration-500 transition-all"
|
||||
:to="$view({ name: 'forgot-password' })"
|
||||
>
|
||||
{{ $t('auth.forgotPassword.ask') }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { Head, Link, useForm } from '@inertiajs/vue3';
|
||||
import { useForm } from '@Services/Api.js'
|
||||
|
||||
import Checkbox from '@Holos/Checkbox.vue';
|
||||
import InputLabel from '@Holos/InputLabel.vue';
|
||||
@ -8,17 +8,19 @@ import PrimaryButton from '@Holos/Button/Primary.vue';
|
||||
import Input from '@Holos/Form/InputWithIcon.vue'
|
||||
import Layout from '@Holos/Layout/AuthLayout.vue'
|
||||
|
||||
/** Propiedades */
|
||||
const form = useForm({
|
||||
name: '',
|
||||
paternal: '',
|
||||
maternal: '',
|
||||
phone: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
terms: false,
|
||||
});
|
||||
|
||||
/** Métodos */
|
||||
const submit = () => {
|
||||
form.post(route('register'), {
|
||||
onFinish: () => form.reset('password', 'password_confirmation'),
|
||||
@ -87,7 +89,7 @@ const submit = () => {
|
||||
/>
|
||||
|
||||
|
||||
<div v-if="$page.props.jetstream.hasTermsAndPrivacyPolicyFeature" class="mt-4">
|
||||
<div class="mt-4">
|
||||
<InputLabel for="terms">
|
||||
<div class="flex items-center">
|
||||
<Checkbox id="terms" v-model:checked="form.terms" name="terms" required />
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { useForm } from '@Services/Api.js'
|
||||
|
||||
import PrimaryButton from '@Holos/Button/Primary.vue';
|
||||
import Input from '@Holos/Form/InputWithIcon.vue'
|
||||
import Layout from '@Holos/Layout/AuthLayout.vue'
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
email: String,
|
||||
token: String,
|
||||
@ -17,10 +17,11 @@ const form = useForm({
|
||||
password_confirmation: '',
|
||||
});
|
||||
|
||||
/** Métodos */
|
||||
const submit = () => {
|
||||
form.post(route('password.update'), {
|
||||
onSuccess: () => {
|
||||
Notify.success(lang('auth.reset.success'));
|
||||
Notify.success(Lang('auth.reset.success'));
|
||||
},
|
||||
onFinish: () => form.reset('password', 'password_confirmation'),
|
||||
});
|
||||
@ -28,38 +29,35 @@ const submit = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout :title="$t('auth.reset.title')">
|
||||
<form @submit.prevent="submit">
|
||||
<Input
|
||||
icon="mail"
|
||||
id="email"
|
||||
type="email"
|
||||
v-model="form.email"
|
||||
:onError="form.errors.email"
|
||||
:placeholder="$t('email.title')"
|
||||
/>
|
||||
<Input
|
||||
icon="password"
|
||||
id="password"
|
||||
type="password"
|
||||
v-model="form.password"
|
||||
:onError="form.errors.password"
|
||||
:placeholder="$t('password')"
|
||||
/>
|
||||
<Input
|
||||
icon="password"
|
||||
id="passwordConfirmation"
|
||||
type="password"
|
||||
v-model="form.password_confirmation"
|
||||
:onError="form.errors.password_confirmation"
|
||||
:placeholder="$t('passwordConfirmation')"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<PrimaryButton class="!w-full" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
|
||||
{{ $t('account.password.update') }}
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</form>
|
||||
</Layout>
|
||||
<form @submit.prevent="submit">
|
||||
<Input
|
||||
icon="mail"
|
||||
id="email"
|
||||
type="email"
|
||||
v-model="form.email"
|
||||
:onError="form.errors.email"
|
||||
:placeholder="$t('email.title')"
|
||||
/>
|
||||
<Input
|
||||
icon="password"
|
||||
id="password"
|
||||
type="password"
|
||||
v-model="form.password"
|
||||
:onError="form.errors.password"
|
||||
:placeholder="$t('password')"
|
||||
/>
|
||||
<Input
|
||||
icon="password"
|
||||
id="passwordConfirmation"
|
||||
type="password"
|
||||
v-model="form.password_confirmation"
|
||||
:onError="form.errors.password_confirmation"
|
||||
:placeholder="$t('passwordConfirmation')"
|
||||
/>
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<PrimaryButton class="!w-full" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
|
||||
{{ $t('account.password.update') }}
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@ -5,17 +5,21 @@ import { Link, useForm } from '@inertiajs/vue3';
|
||||
import PrimaryButton from '@Holos/Button/Primary.vue';
|
||||
import Layout from '@Holos/Layout/AuthLayout.vue';
|
||||
|
||||
/** Propiedades */
|
||||
const props = defineProps({
|
||||
status: String,
|
||||
});
|
||||
|
||||
const form = useForm({});
|
||||
|
||||
/** Propiedades computadas */
|
||||
const verificationLinkSent = computed(() => props.status === 'verification-link-sent');
|
||||
|
||||
/** Métodos */
|
||||
const submit = () => {
|
||||
form.post(route('verification.send'));
|
||||
};
|
||||
|
||||
const verificationLinkSent = computed(() => props.status === 'verification-link-sent');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
<script setup>
|
||||
import AppLayout from '@Layouts/AppLayout.vue';
|
||||
import PageHeader from '@Holos/PageHeader.vue';
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Dashboard">
|
||||
<template #header>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ $t('dashboard') }}
|
||||
</h2>
|
||||
</template>
|
||||
</AppLayout>
|
||||
</template>
|
||||
<PageHeader title="Dashboard" />
|
||||
<p v-html="$t('welcome', { name: $page.user.name })"></p>
|
||||
</template>
|
||||
@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { transl, can, goTo } from './Module'
|
||||
import { transl, can, viewTo } from './Module'
|
||||
|
||||
import ModalController from '@Controllers/ModalController.js';
|
||||
import SearcherController from '@Controllers/SearcherController.js';
|
||||
@ -18,7 +18,7 @@ import GoogleIcon from '@/Components/Shared/GoogleIcon.vue';
|
||||
|
||||
/** Definidores */
|
||||
const inboxCtl = new InboxController();
|
||||
const searcherCtl = new SearcherController(goTo('index'));
|
||||
const searcherCtl = new SearcherController(viewTo('index'));
|
||||
|
||||
/** Eventos */
|
||||
const props = defineProps({
|
||||
|
||||
@ -2,7 +2,7 @@ import { lang } from '@/Lang/i18n';
|
||||
import { hasPermission } from '@Plugins/RolePermission.js';
|
||||
|
||||
// Obtener ruta
|
||||
const goTo = (route) => `admin.users.${route}`
|
||||
const viewTo = (route) => `admin.users.${route}`
|
||||
// Obtener traducción del componente
|
||||
const transl = (str) => lang(`notifications.${str}`)
|
||||
// Determina si un usuario puede hacer algo no en base a los permisos
|
||||
@ -10,6 +10,6 @@ const can = (permission) => hasPermission(`users.${permission}`)
|
||||
|
||||
export {
|
||||
can,
|
||||
goTo,
|
||||
viewTo,
|
||||
transl
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { useForm } from '@Services/Api';
|
||||
import { logout } from '@Services/Page';
|
||||
|
||||
import ActionSection from '@Holos/ActionSection.vue';
|
||||
import DangerButton from '@Holos/Button/Danger.vue';
|
||||
@ -22,9 +23,12 @@ const confirmUserDeletion = () => {
|
||||
};
|
||||
|
||||
const deleteUser = () => {
|
||||
form.delete(route('current-user.destroy'), {
|
||||
form.delete(route('user.destroy'), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => closeModal(),
|
||||
onSuccess: () => {
|
||||
closeModal();
|
||||
logout();
|
||||
},
|
||||
onError: () => passwordInput.value.focus(),
|
||||
onFinish: () => form.reset(),
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { useForm } from '@Services/Api';
|
||||
|
||||
import ActionSection from '@Holos/ActionSection.vue';
|
||||
import DialogModal from '@Holos/DialogModal.vue';
|
||||
@ -9,7 +9,10 @@ import SecondaryButton from '@Holos/Button/Secondary.vue';
|
||||
import Input from '@Holos/Form/Input.vue';
|
||||
|
||||
defineProps({
|
||||
sessions: Array,
|
||||
sessions: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const confirmingLogout = ref(false);
|
||||
@ -30,7 +33,7 @@ const logoutOtherBrowserSessions = () => {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
form.reset();
|
||||
Notify.success(lang('account.sessions.done'));
|
||||
Notify.success(Lang('account.sessions.done'));
|
||||
},
|
||||
onError: () => passwordInput.value.focus(),
|
||||
onFinish: () => form.reset(),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { router, useForm, usePage } from '@inertiajs/vue3';
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { api, useForm } from '@Services/Api';
|
||||
|
||||
import ActionSection from '@Holos/ActionSection.vue';
|
||||
import ConfirmsPassword from '@Holos/ConfirmsPassword.vue';
|
||||
@ -10,37 +10,40 @@ import SecondaryButton from '@Holos/Button/Secondary.vue';
|
||||
import Input from '@Holos/Form/Input.vue';
|
||||
|
||||
const props = defineProps({
|
||||
requiresConfirmation: Boolean,
|
||||
requiresConfirmation: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const page = usePage();
|
||||
const enabling = ref(false);
|
||||
const confirming = ref(false);
|
||||
const disabling = ref(false);
|
||||
const qrCode = ref(null);
|
||||
const setupKey = ref(null);
|
||||
const recoveryCodes = ref([]);
|
||||
const user = ref(null);
|
||||
|
||||
const confirmationForm = useForm({
|
||||
code: '',
|
||||
});
|
||||
|
||||
const twoFactorEnabled = computed(
|
||||
() => ! enabling.value && page.props.auth.user?.two_factor_enabled,
|
||||
() => ! enabling.value && user.value?.two_factor_secret,
|
||||
);
|
||||
|
||||
watch(twoFactorEnabled, () => {
|
||||
if (! twoFactorEnabled.value) {
|
||||
confirmationForm.reset();
|
||||
confirmationForm.clearErrors();
|
||||
}
|
||||
});
|
||||
|
||||
const enableTwoFactorAuthentication = () => {
|
||||
enabling.value = true;
|
||||
|
||||
router.post(route('two-factor.enable'), {}, {
|
||||
preserveScroll: true,
|
||||
console.log('enabling ...');
|
||||
|
||||
api.post(route('two-factor.enable'), {
|
||||
onSuccess: () => Promise.all([
|
||||
showQrCode(),
|
||||
showSetupKey(),
|
||||
@ -93,7 +96,7 @@ const regenerateRecoveryCodes = () => {
|
||||
const disableTwoFactorAuthentication = () => {
|
||||
disabling.value = true;
|
||||
|
||||
router.delete(route('two-factor.disable'), {
|
||||
api.delete(route('two-factor.disable'), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
disabling.value = false;
|
||||
@ -101,6 +104,14 @@ const disableTwoFactorAuthentication = () => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
api.get(route('user.show'), {
|
||||
onSuccess: (r) => {
|
||||
user.value = r.user;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { useForm } from '@Services/Api';
|
||||
|
||||
import FormSection from '@Holos/FormSection.vue';
|
||||
import PrimaryButton from '@Holos/Button/Primary.vue';
|
||||
@ -16,14 +16,13 @@ const form = useForm({
|
||||
});
|
||||
|
||||
const updatePassword = () => {
|
||||
form.put(route('user-password.update'), {
|
||||
errorBag: 'updatePassword',
|
||||
preserveScroll: true,
|
||||
form.put(route('user.password'), {
|
||||
onSuccess: () => {
|
||||
form.reset();
|
||||
Notify.success(lang('account.password.updated'));
|
||||
Notify.success(Lang('account.password.updated'));
|
||||
},
|
||||
onError: () => {
|
||||
onError: (e) => {
|
||||
console.log(e);
|
||||
if (form.errors.password) {
|
||||
form.reset('password', 'password_confirmation');
|
||||
passwordInput.value.focus();
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { Link, router, useForm } from '@inertiajs/vue3';
|
||||
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { api, useForm } from '@Services/Api';
|
||||
import { reloadUser } from '@Services/Page';
|
||||
import FormSection from '@Holos/FormSection.vue';
|
||||
import Input from '@Holos/Form/Input.vue';
|
||||
import Error from '@Holos/Form/Elements/Error.vue';
|
||||
@ -9,42 +9,34 @@ import Label from '@Holos/Form/Elements/Label.vue';
|
||||
import PrimaryButton from '@Holos/Button/Primary.vue';
|
||||
import SecondaryButton from '@Holos/Button/Secondary.vue';
|
||||
|
||||
const props = defineProps({
|
||||
user: Object,
|
||||
});
|
||||
|
||||
/** Propiedades */
|
||||
const form = useForm({
|
||||
_method: 'PUT',
|
||||
name: props.user.name,
|
||||
paternal: props.user.paternal,
|
||||
maternal: props.user.maternal,
|
||||
phone: props.user.phone,
|
||||
name: props.user.name,
|
||||
email: props.user.email,
|
||||
name: '',
|
||||
paternal: '',
|
||||
maternal: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
photo: null,
|
||||
});
|
||||
|
||||
const verificationLinkSent = ref(null);
|
||||
const photoInput = ref(null);
|
||||
const photoPreview = ref(null);
|
||||
const photoInput = ref(null);
|
||||
|
||||
/** Métodos */
|
||||
const updateProfileInformation = () => {
|
||||
if (photoInput.value) {
|
||||
form.photo = photoInput.value.files[0];
|
||||
}
|
||||
|
||||
form.post(route('user-profile-information.update'), {
|
||||
errorBag: 'updateProfileInformation',
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
form.post(route('user.update'), {
|
||||
onFinish: () => {
|
||||
reloadUser();
|
||||
clearPhotoFileInput();
|
||||
Notify.success(lang('account.profile.updated'));
|
||||
},
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const sendEmailVerification = () => {
|
||||
verificationLinkSent.value = true;
|
||||
Notify.success(Lang('account.profile.updated'));
|
||||
};
|
||||
|
||||
const selectNewPhoto = () => {
|
||||
@ -66,12 +58,12 @@ const updatePhotoPreview = () => {
|
||||
};
|
||||
|
||||
const deletePhoto = () => {
|
||||
router.delete(route('current-user-photo.destroy'), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
api.delete(route('user.photo'), {
|
||||
onFinish: () => {
|
||||
photoPreview.value = null;
|
||||
reloadUser();
|
||||
clearPhotoFileInput();
|
||||
},
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -80,6 +72,14 @@ const clearPhotoFileInput = () => {
|
||||
photoInput.value.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
api.get(route('user.show'), {
|
||||
onSuccess: (r) => {
|
||||
form.fill(r.user);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -94,7 +94,7 @@ const clearPhotoFileInput = () => {
|
||||
|
||||
<template #form>
|
||||
<!-- Profile Photo -->
|
||||
<div v-if="$page.props.jetstream.managesProfilePhotos" class="col-span-6 sm:col-span-4">
|
||||
<div v-if="$page.user" class="col-span-6 sm:col-span-4">
|
||||
<!-- Profile Photo File Input -->
|
||||
<input
|
||||
id="photo"
|
||||
@ -111,7 +111,7 @@ const clearPhotoFileInput = () => {
|
||||
|
||||
<!-- Current Profile Photo -->
|
||||
<div v-show="! photoPreview" class="mt-2">
|
||||
<img :src="user.profile_photo_url" :alt="user.name" class="rounded-full h-20 w-20 object-cover">
|
||||
<img :src="$page.user.profile_photo_url" :alt="$page.user.name" class="rounded-full h-20 w-20 object-cover">
|
||||
</div>
|
||||
|
||||
<!-- New Profile Photo Preview -->
|
||||
@ -127,7 +127,7 @@ const clearPhotoFileInput = () => {
|
||||
</SecondaryButton>
|
||||
|
||||
<SecondaryButton
|
||||
v-if="user.profile_photo_path"
|
||||
v-if="$page.user.profile_photo_path"
|
||||
type="button"
|
||||
class="mt-2"
|
||||
@click.prevent="deletePhoto"
|
||||
@ -180,26 +180,6 @@ const clearPhotoFileInput = () => {
|
||||
:onError="form.errors.email"
|
||||
required
|
||||
/>
|
||||
|
||||
<div v-if="$page.props.jetstream.hasEmailVerification && user.email_verified_at === null">
|
||||
<p class="text-sm mt-2">
|
||||
{{ $t('account.email.unverify') }}
|
||||
|
||||
<Link
|
||||
:href="route('verification.send')"
|
||||
method="post"
|
||||
as="button"
|
||||
class="underline text-sm text-page-t/50 hover:text-page-t dark:text-page-dt/50 dark:hover:text-page-dt rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click.prevent="sendEmailVerification"
|
||||
>
|
||||
{{ $t('account.email.sendVerification') }}
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<div v-show="verificationLinkSent" class="mt-2 font-medium text-sm text-green-600">
|
||||
{{ $t('account.email.notifySendVerification') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<script setup>
|
||||
import SectionBorder from '@Holos/SectionBorder.vue';
|
||||
import AppLayout from '@Layouts/AppLayout.vue';
|
||||
import DeleteUserForm from '@Pages/Profile/Partials/DeleteUserForm.vue';
|
||||
import LogoutOtherBrowserSessionsForm from '@Pages/Profile/Partials/LogoutOtherBrowserSessionsForm.vue';
|
||||
import TwoFactorAuthenticationForm from '@Pages/Profile/Partials/TwoFactorAuthenticationForm.vue';
|
||||
import UpdatePasswordForm from '@Pages/Profile/Partials/UpdatePasswordForm.vue';
|
||||
import UpdateProfileInformationForm from '@Pages/Profile/Partials/UpdateProfileInformationForm.vue';
|
||||
import LogoutOtherBrowserSessionsForm from '@Pages/Profile/Partials/LogoutOtherBrowserSessionsForm.vue';
|
||||
import PageHeader from '@Holos/PageHeader.vue';
|
||||
|
||||
defineProps({
|
||||
confirmsTwoFactorAuthentication: Boolean,
|
||||
@ -14,44 +14,25 @@ defineProps({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title="Profile">
|
||||
<template #header>
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
Profile
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<PageHeader :title="$t('profile')" />
|
||||
<div>
|
||||
<div>
|
||||
<div class="py-10 sm:px-6 lg:px-8">
|
||||
<div v-if="$page.props.jetstream.canUpdateProfileInformation">
|
||||
<UpdateProfileInformationForm :user="$page.props.auth.user" />
|
||||
|
||||
<SectionBorder />
|
||||
</div>
|
||||
|
||||
<div v-if="$page.props.jetstream.canUpdatePassword">
|
||||
<UpdatePasswordForm class="mt-10 sm:mt-0" />
|
||||
|
||||
<SectionBorder />
|
||||
</div>
|
||||
|
||||
<div v-if="$page.props.jetstream.canManageTwoFactorAuthentication">
|
||||
<TwoFactorAuthenticationForm
|
||||
:requires-confirmation="confirmsTwoFactorAuthentication"
|
||||
class="mt-10 sm:mt-0"
|
||||
/>
|
||||
|
||||
<SectionBorder />
|
||||
</div>
|
||||
|
||||
<LogoutOtherBrowserSessionsForm :sessions="sessions" class="mt-10 sm:mt-0" />
|
||||
|
||||
<template v-if="$page.props.jetstream.hasAccountDeletionFeatures">
|
||||
<SectionBorder />
|
||||
|
||||
<DeleteUserForm class="mt-10 sm:mt-0" />
|
||||
</template>
|
||||
</div>
|
||||
<UpdateProfileInformationForm :user="$page.user" />
|
||||
<SectionBorder />
|
||||
</div>
|
||||
</AppLayout>
|
||||
<div>
|
||||
<UpdatePasswordForm class="mt-10 sm:mt-0" />
|
||||
<SectionBorder />
|
||||
</div>
|
||||
<!-- <div>
|
||||
<TwoFactorAuthenticationForm
|
||||
:requires-confirmation="confirmsTwoFactorAuthentication"
|
||||
class="mt-10 sm:mt-0"
|
||||
/>
|
||||
<SectionBorder />
|
||||
</div> -->
|
||||
<!-- <LogoutOtherBrowserSessionsForm :sessions="sessions" class="mt-10 sm:mt-0" /> -->
|
||||
<SectionBorder />
|
||||
<DeleteUserForm class="mt-10 sm:mt-0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -4,7 +4,7 @@ import toastr from 'toastr';
|
||||
class Notify {
|
||||
constructor() {}
|
||||
|
||||
flash({message = 'Successful registration', type = 'success', timeout = 5, title= lang('notification')}) {
|
||||
flash({message = 'Successful registration', type = 'success', timeout = 5, title= Lang('notification')}) {
|
||||
|
||||
toastr.options = {
|
||||
"closeButton": true,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ref } from 'vue';
|
||||
import { api } from '@Services/Api';
|
||||
|
||||
const permissionsInit = ref(false)
|
||||
const allPermissions = ref([])
|
||||
@ -22,10 +23,13 @@ const hasPermission = (can) => {
|
||||
|
||||
const bootPermissions = () => {
|
||||
if (!permissionsInit.value) {
|
||||
axios.get(route('system.permissions')).then((res) => {
|
||||
loadPermissions(res.data.data.permissions)
|
||||
|
||||
permissionsInit.value = true;
|
||||
api.get(route('user.permissions'), {
|
||||
onSuccess: (res) => {
|
||||
loadPermissions(res.permissions)
|
||||
},
|
||||
onFinish: () => {
|
||||
permissionsInit.value = true;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
24
src/router/Auth.js
Normal file
24
src/router/Auth.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'index',
|
||||
component: () => import('@Pages/Auth/Login.vue')
|
||||
},
|
||||
{
|
||||
path: '/forgot-password',
|
||||
name: 'forgot-password',
|
||||
component: () => import('@Pages/Auth/ForgotPassword.vue')
|
||||
},
|
||||
{
|
||||
path: '/reset-password',
|
||||
name: 'reset-password',
|
||||
component: () => import('@Pages/Auth/ResetPassword.vue')
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
export default router
|
||||
45
src/router/Index.js
Normal file
45
src/router/Index.js
Normal file
@ -0,0 +1,45 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'index',
|
||||
component: () => import('@Pages/Dashboard/Index.vue')
|
||||
}, {
|
||||
path: '/profile',
|
||||
name: 'profile.show',
|
||||
component: () => import('@Pages/Profile/Show.vue')
|
||||
}, {
|
||||
path: '/admin',
|
||||
children: [
|
||||
{
|
||||
path: 'users',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'admin.users.index',
|
||||
component: () => import('@Pages/Admin/Users/Index.vue')
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
name: 'admin.users.create',
|
||||
component: () => import('@Pages/Admin/Users/Create.vue')
|
||||
}, {
|
||||
path: ':id/edit',
|
||||
name: 'admin.users.edit',
|
||||
component: () => import('@Pages/Admin/Users/Edit.vue')
|
||||
}, {
|
||||
path: ':id/settings',
|
||||
name: 'admin.users.settings',
|
||||
component: () => import('@Pages/Admin/Users/Settings.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
export default router
|
||||
@ -7,7 +7,6 @@
|
||||
|
||||
import axios from 'axios';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { lang } from '@Lang/i18n';
|
||||
|
||||
axios.defaults.withXSRFToken = true;
|
||||
// axios.defaults.withCredentials = true;
|
||||
@ -18,20 +17,14 @@ axios.defaults.withXSRFToken = true;
|
||||
const failCodes = [
|
||||
400,
|
||||
409,
|
||||
422
|
||||
];
|
||||
|
||||
/**
|
||||
* Servidor a utilizar
|
||||
*/
|
||||
const server = ref('');
|
||||
const token = ref(localStorage.token);
|
||||
|
||||
/**
|
||||
* Define el servidor de la api
|
||||
*/
|
||||
const defineApiServer = (x) => {
|
||||
server.value = x;
|
||||
}
|
||||
const token = ref(localStorage.token);
|
||||
const csrfToken = ref(localStorage.csrfToken);
|
||||
|
||||
/**
|
||||
* Define el token de la api
|
||||
@ -42,21 +35,13 @@ const defineApiToken = (x) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruta base del servidor
|
||||
* Define CSRF token
|
||||
*/
|
||||
const apiBaseUrl = (url) => {
|
||||
return `${server.value}/${url}`
|
||||
const defineCsrfToken = (x) => {
|
||||
csrfToken.value = x;
|
||||
localStorage.csrfToken = x;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ruta api del servidor
|
||||
*/
|
||||
const apiUrl = (url) => {
|
||||
return apiBaseUrl(`api/${url}`)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Define el token de la api
|
||||
*/
|
||||
@ -65,6 +50,14 @@ const resetApiToken = () => {
|
||||
localStorage.removeItem('token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset CSRF token
|
||||
*/
|
||||
const resetCsrfToken = () => {
|
||||
csrfToken.value = undefined;
|
||||
localStorage.removeItem('csrfToken');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determina si el token tiene algo o no
|
||||
*/
|
||||
@ -75,9 +68,13 @@ const hasToken = () => {
|
||||
/**
|
||||
* Fuerza el cierre de la sesión
|
||||
*/
|
||||
const logout = () => {
|
||||
localStorage.removeItem('token');
|
||||
token.value = undefined;
|
||||
const closeSession = () => {
|
||||
resetApiToken()
|
||||
resetCsrfToken()
|
||||
|
||||
Notify.info(Lang('session.closed'))
|
||||
|
||||
location.replace('auth.html')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,6 +135,8 @@ const api = {
|
||||
if(options.hasOwnProperty('onFail')) {
|
||||
options.onFail(data.data);
|
||||
}
|
||||
|
||||
console.log(data.data);
|
||||
}
|
||||
|
||||
if(options.hasOwnProperty('onFinish')) {
|
||||
@ -152,9 +151,9 @@ const api = {
|
||||
|
||||
// Código de sesión invalida
|
||||
if(response.status === 401 && response.data?.message == 'Unauthenticated.') {
|
||||
Notify.error(lang('session.expired'));
|
||||
Notify.error(Lang('session.expired'));
|
||||
|
||||
logout();
|
||||
closeSession();
|
||||
|
||||
return
|
||||
}
|
||||
@ -182,35 +181,35 @@ const api = {
|
||||
get(url, options) {
|
||||
this.load({
|
||||
method: 'get',
|
||||
url: apiUrl(url),
|
||||
url,
|
||||
options
|
||||
})
|
||||
},
|
||||
post(url, options) {
|
||||
this.load({
|
||||
method: 'post',
|
||||
url: apiUrl(url),
|
||||
url,
|
||||
options
|
||||
})
|
||||
},
|
||||
put(url, options) {
|
||||
this.load({
|
||||
method: 'put',
|
||||
url: apiUrl(url),
|
||||
url,
|
||||
options
|
||||
})
|
||||
},
|
||||
patch(url, options) {
|
||||
this.load('patch', {
|
||||
method: 'patch',
|
||||
url: apiUrl(url),
|
||||
url,
|
||||
options
|
||||
})
|
||||
},
|
||||
delete(url, options) {
|
||||
this.load({
|
||||
method: 'delete',
|
||||
url: apiUrl(url),
|
||||
url,
|
||||
options
|
||||
})
|
||||
},
|
||||
@ -219,7 +218,6 @@ const api = {
|
||||
...options,
|
||||
data: resources
|
||||
})
|
||||
console.log(api.resource)
|
||||
},
|
||||
download(url, file, params = {}) {
|
||||
axios({
|
||||
@ -310,6 +308,14 @@ const useForm = (form = {}) => {
|
||||
processing: false,
|
||||
wasSuccessful: false,
|
||||
_inputs: Object.keys(form),
|
||||
_original: {
|
||||
...form
|
||||
},
|
||||
reset() {
|
||||
for(let i in this._original) {
|
||||
this[i] = this._original[i]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let data = {};
|
||||
|
||||
@ -354,7 +360,8 @@ const useForm = (form = {}) => {
|
||||
? 'multipart/form-data boundary='
|
||||
: 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Bearer ${apiToken}`
|
||||
'Authorization': `Bearer ${apiToken}`,
|
||||
'X-CSRF-TOKEN': csrfToken.value
|
||||
}
|
||||
});
|
||||
|
||||
@ -397,45 +404,43 @@ const useForm = (form = {}) => {
|
||||
},
|
||||
fill(model) {
|
||||
this._inputs.forEach(element => {
|
||||
if (element == 'is_active') {
|
||||
this[element] = (model[element] == 1)
|
||||
} else {
|
||||
this[element] = model[element] ?? this[element]
|
||||
}
|
||||
this[element] = (element == 'is_active')
|
||||
? (model[element] == 1)
|
||||
: model[element] ?? this[element]
|
||||
});
|
||||
},
|
||||
get(url, options) {
|
||||
this.load({
|
||||
method: 'get',
|
||||
url: apiUrl(url),
|
||||
url,
|
||||
options
|
||||
})
|
||||
},
|
||||
post(url, options) {
|
||||
this.load({
|
||||
method: 'post',
|
||||
url: apiUrl(url),
|
||||
url,
|
||||
options
|
||||
})
|
||||
},
|
||||
put(url, options) {
|
||||
this.load({
|
||||
method: 'put',
|
||||
url: apiUrl(url),
|
||||
url,
|
||||
options
|
||||
})
|
||||
},
|
||||
patch(url, options) {
|
||||
this.load('patch', {
|
||||
method: 'patch',
|
||||
url: apiUrl(url),
|
||||
url,
|
||||
options
|
||||
})
|
||||
},
|
||||
delete(url, options) {
|
||||
this.load({
|
||||
method: 'delete',
|
||||
url: apiUrl(url),
|
||||
url,
|
||||
options
|
||||
})
|
||||
},
|
||||
@ -507,8 +512,8 @@ const useSearcher = (options = {
|
||||
|
||||
// Código de sesión invalida
|
||||
if(response.status === 401 && response.data.message == 'Unauthenticated.') {
|
||||
Notify.error(lang('session.expired'));
|
||||
logout();
|
||||
Notify.error(Lang('session.expired'));
|
||||
closeSession();
|
||||
return
|
||||
}
|
||||
|
||||
@ -535,13 +540,13 @@ const useSearcher = (options = {
|
||||
search(q, filters = {}) {
|
||||
this.query = q
|
||||
this.load({
|
||||
url: apiUrl(options.url),
|
||||
url,
|
||||
filters
|
||||
})
|
||||
},
|
||||
refresh(filters = {}) {
|
||||
this.load({
|
||||
url: apiUrl(options.url),
|
||||
url,
|
||||
filters
|
||||
})
|
||||
},
|
||||
@ -550,12 +555,11 @@ const useSearcher = (options = {
|
||||
export {
|
||||
api,
|
||||
token,
|
||||
apiBaseUrl,
|
||||
apiUrl,
|
||||
closeSession,
|
||||
hasToken,
|
||||
useForm,
|
||||
useSearcher,
|
||||
defineApiServer,
|
||||
defineApiToken,
|
||||
defineCsrfToken,
|
||||
resetApiToken
|
||||
}
|
||||
40
src/services/Broadcast.js
Normal file
40
src/services/Broadcast.js
Normal file
@ -0,0 +1,40 @@
|
||||
import Echo from 'laravel-echo';
|
||||
import Pusher from 'pusher-js';
|
||||
import { token } from '@Services/Api';
|
||||
|
||||
window.Pusher = Pusher;
|
||||
|
||||
window.Echo = new Echo({
|
||||
broadcaster: 'reverb',
|
||||
authorizer: (channel, options) => {
|
||||
return {
|
||||
authorize: async (socketId, callback) => {
|
||||
try {
|
||||
let { data } = await axios({
|
||||
method: 'post',
|
||||
url: import.meta.env.VITE_REVERB_SCHEME + '://' + import.meta.env.VITE_REVERB_HOST + '/broadcasting/auth',
|
||||
data: {
|
||||
socket_id: socketId,
|
||||
channel_name: channel.name,
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `Bearer ${token.value}`
|
||||
}
|
||||
});
|
||||
|
||||
callback(null, data);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
key: import.meta.env.VITE_REVERB_APP_KEY,
|
||||
wsHost: import.meta.env.VITE_REVERB_HOST,
|
||||
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
|
||||
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
|
||||
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
|
||||
enabledTransports: ['ws', 'wss'],
|
||||
});
|
||||
116
src/services/Page.js
Normal file
116
src/services/Page.js
Normal file
@ -0,0 +1,116 @@
|
||||
import { reactive } from "vue";
|
||||
import { api, closeSession } from '@Services/Api';
|
||||
import { resetPermissions } from '@Plugins/RolePermission';
|
||||
|
||||
/**
|
||||
* Cache
|
||||
*
|
||||
* Permite cargar los datos una vez para la sesión actual. Se mantienen mientras
|
||||
* no se recargue la página.
|
||||
*/
|
||||
const page = reactive({
|
||||
lang: 'es',
|
||||
user: {
|
||||
id: 0,
|
||||
name: 'public'
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Recarga datos en cache
|
||||
*/
|
||||
const reloadApp = () => {
|
||||
const user = localStorage.user
|
||||
|
||||
if(user) {
|
||||
page.user = JSON.parse(user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpiar sesión de usuario
|
||||
*/
|
||||
const resetPage = () => {
|
||||
localStorage.removeItem('user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Permite buscar una opcionalmente
|
||||
*/
|
||||
const view = ({
|
||||
name = '',
|
||||
params = {},
|
||||
query = {},
|
||||
}) => ({
|
||||
name: name,
|
||||
params,
|
||||
query,
|
||||
})
|
||||
|
||||
/**
|
||||
* Almacenar datos usuario
|
||||
*/
|
||||
const defineUser = (user) => {
|
||||
localStorage.user = JSON.stringify({
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
lastname: `${user.paternal} ${user?.maternal ?? ''}`,
|
||||
email: user.email,
|
||||
phone: user.phone,
|
||||
profile_photo_url: user.profile_photo_url,
|
||||
profile_photo_path: user.profile_photo_path,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Instalar el componente de forma nativa
|
||||
*/
|
||||
const pagePlugin = {
|
||||
install: (app, options) => {
|
||||
app.config.globalProperties.$page = page;
|
||||
app.config.globalProperties.$view = view;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload user
|
||||
*/
|
||||
const reloadUser = () => {
|
||||
console.log('reloadUser')
|
||||
|
||||
return api.get(route('user.show'), {
|
||||
onSuccess: (r) => {
|
||||
defineUser(r.user)
|
||||
reloadApp()
|
||||
|
||||
return r.user;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cerrar sesión
|
||||
*/
|
||||
const logout = () => {
|
||||
resetPermissions()
|
||||
resetPage()
|
||||
|
||||
api.post(route('auth.logout'), {
|
||||
onSuccess: (r) => {
|
||||
if(r.is_revoked === true) {
|
||||
closeSession()
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
pagePlugin,
|
||||
page,
|
||||
reloadApp,
|
||||
reloadUser,
|
||||
resetPage,
|
||||
defineUser,
|
||||
logout,
|
||||
view
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user