Initial commit

This commit is contained in:
Lizandro Guarnizo
2025-02-06 14:22:29 -05:00
commit 6d8f1bcd6f
452 changed files with 250835 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File
View File
View File
+11
View File
@@ -0,0 +1,11 @@
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
+32
View File
@@ -0,0 +1,32 @@
import Vue from 'vue'
import App from './App.vue'
import Router from 'vue-router'
import {VuejsDatatableFactory} from 'vuejs-datatable';
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
import store from './store'
import routes from './routes'
import "./icons"
Vue.component('FontAwesome', FontAwesomeIcon)
Vue.config.productionTip = false
Vue.use(Router)
VuejsDatatableFactory.useDefaultType(false)
.registerTableType('datatable', tableType => tableType.mergeSettings({
table: {class: 'datatable table-fixed w-full'},
}));
Vue.use(VuejsDatatableFactory);
const router = new Router({
mode: 'hash',
routes
})
new Vue({
render: h => h(App),
router,
store
}).$mount('#app')
+41
View File
@@ -0,0 +1,41 @@
<template>
<div class="leading-normal tracking-normal" id="main-body">
<div class="flex flex-wrap">
<Sidebar/>
<div class="w-full bg-gray-100 pl-0 lg:pl-64 min-h-screen" :class="sideBarOpen ? 'overlay' : ''"
id="main-content">
<Navbar/>
<div class="p-6 bg-gray-100 mb-20">
<router-view/>
</div>
<Footer/>
</div>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex'
import Sidebar from './Sidebar'
import Navbar from './Navbar'
import Footer from './Footer'
export default {
name: 'Dashboard',
computed: {
...mapState(['sideBarOpen'])
},
components: {
Sidebar,
Navbar,
Footer
}
}
</script>
+17
View File
@@ -0,0 +1,17 @@
<template>
<div class="w-full border-t-2 px-8 py-6 lg:flex justify-between items-center">
<p class="mb-2 lg:mb-0">© Copyright 2020</p>
<div class="flex">
<a href="#" class="mr-6 hover:text-gray-900">Terms of Service</a>
<a href="#" class="mr-6 hover:text-gray-900">Privacy Policy</a>
<a href="#" class="hover:text-gray-900">About Us</a>
</div>
</div>
</template>
<script>
export default {
name: 'Footer'
}
</script>
+78
View File
@@ -0,0 +1,78 @@
<template>
<div class="sticky top-0 z-40">
<div class="w-full h-20 px-6 bg-gray-100 border-b flex items-center justify-between">
<!-- left navbar -->
<div class="flex">
<!-- mobile hamburger -->
<div class="inline-block lg:hidden flex items-center mr-4">
<button class="hover:text-blue-500 hover:border-white focus:outline-none navbar-burger"
@click="toggleSidebar()">
<svg class="h-5 w-5" v-bind:style="{ fill: 'black' }" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"><title>Menu</title>
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"/>
</svg>
</button>
</div>
<!-- search bar -->
<div class="relative text-gray-600">
<input type="search" name="serch" placeholder="Search products..."
class="bg-white h-10 w-full xl:w-64 px-5 rounded-lg border text-sm focus:outline-none">
<button type="submit" class="absolute right-0 top-0 mt-3 mr-4">
<svg class="h-4 w-4 fill-current" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px"
viewBox="0 0 56.966 56.966" style="enable-background:new 0 0 56.966 56.966;"
xml:space="preserve" width="512px" height="512px">
<path d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23 s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92 c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17 s-17-7.626-17-17S14.61,6,23.984,6z"/>
</svg>
</button>
</div>
</div>
<!-- right navbar -->
<div class="flex items-center relative">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"
class="fill-current mr-3 hover:text-blue-500">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2zm-2 1H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6z"/>
</svg>
<img src="https://a7sas.net/wp-content/uploads/2019/07/4060.jpeg"
class="w-12 h-12 rounded-full shadow-lg" @click="dropDownOpen = !dropDownOpen">
</div>
</div>
<!-- dropdown menu -->
<div class="absolute bg-gray-100 border border-t-0 shadow-xl text-gray-700 rounded-b-lg w-48 bottom-10 right-0 mr-6"
:class="dropDownOpen ? '' : 'hidden'">
<a href="#" class="block px-4 py-2 hover:bg-gray-200">Account</a>
<a href="#" class="block px-4 py-2 hover:bg-gray-200">Settings</a>
<a href="#" class="block px-4 py-2 hover:bg-gray-200">Logout</a>
</div>
<!-- dropdown menu end -->
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'Navbar',
computed: {
...mapState(['sideBarOpen'])
},
data() {
return {
dropDownOpen: false
}
},
methods: {
toggleSidebar() {
this.$store.dispatch('toggleSidebar')
}
}
}
</script>
+148
View File
@@ -0,0 +1,148 @@
<template>
<!-- give the sidebar z-50 class so its higher than the navbar if you want to see the logo -->
<!-- you will need to add a little "X" button next to the logo in order to close it though -->
<div
class="w-1/2 md:w-1/3 lg:w-64 fixed md:top-0 md:left-0 h-screen lg:block bg-gray-100 border-r z-30"
:class="sideBarOpen ? '' : 'hidden'"
id="main-nav"
>
<div class="w-full h-20 border-b flex px-4 items-center mb-8">
<router-link to="/dashboard/home">
<div class="font-semibold text-3xl text-blue-400 pl-4">LOGO</div>
</router-link>
</div>
<div class="mb-4 px-4">
<p class="pl-4 text-sm font-semibold mb-1">MAIN</p>
<router-link to="/dashboard/home">
<div
class="w-full flex items-center text-blue-400 h-10 pl-4 bg-gray-200 hover:bg-gray-200 rounded-lg cursor-pointer"
>
<svg class="h-6 w-6 fill-current mr-2" viewBox="0 0 20 20">
<path
d="M18.121,9.88l-7.832-7.836c-0.155-0.158-0.428-0.155-0.584,0L1.842,9.913c-0.262,0.263-0.073,0.705,0.292,0.705h2.069v7.042c0,0.227,0.187,0.414,0.414,0.414h3.725c0.228,0,0.414-0.188,0.414-0.414v-3.313h2.483v3.313c0,0.227,0.187,0.414,0.413,0.414h3.726c0.229,0,0.414-0.188,0.414-0.414v-7.042h2.068h0.004C18.331,10.617,18.389,10.146,18.121,9.88 M14.963,17.245h-2.896v-3.313c0-0.229-0.186-0.415-0.414-0.415H8.342c-0.228,0-0.414,0.187-0.414,0.415v3.313H5.032v-6.628h9.931V17.245z M3.133,9.79l6.864-6.868l6.867,6.868H3.133z"
/>
</svg>
<span class="text-gray-700">Dashboard</span>
</div>
</router-link>
<div
class="w-full flex items-center text-blue-400 h-10 pl-4 hover:bg-gray-200 rounded-lg cursor-pointer"
>
<svg class="h-6 w-6 fill-current mr-2" viewBox="0 0 20 20">
<path
d="M17.431,2.156h-3.715c-0.228,0-0.413,0.186-0.413,0.413v6.973h-2.89V6.687c0-0.229-0.186-0.413-0.413-0.413H6.285c-0.228,0-0.413,0.184-0.413,0.413v6.388H2.569c-0.227,0-0.413,0.187-0.413,0.413v3.942c0,0.228,0.186,0.413,0.413,0.413h14.862c0.228,0,0.413-0.186,0.413-0.413V2.569C17.844,2.342,17.658,2.156,17.431,2.156 M5.872,17.019h-2.89v-3.117h2.89V17.019zM9.587,17.019h-2.89V7.1h2.89V17.019z M13.303,17.019h-2.89v-6.651h2.89V17.019z M17.019,17.019h-2.891V2.982h2.891V17.019z"
/>
</svg>
<span class="text-gray-700">Analytics</span>
</div>
<div
class="w-full flex items-center text-blue-400 h-10 pl-4 hover:bg-gray-200 rounded-lg cursor-pointer"
>
<svg class="h-6 w-6 fill-current mr-2" viewBox="0 0 20 20">
<path
d="M17.283,5.549h-5.26V4.335c0-0.222-0.183-0.404-0.404-0.404H8.381c-0.222,0-0.404,0.182-0.404,0.404v1.214h-5.26c-0.223,0-0.405,0.182-0.405,0.405v9.71c0,0.223,0.182,0.405,0.405,0.405h14.566c0.223,0,0.404-0.183,0.404-0.405v-9.71C17.688,5.731,17.506,5.549,17.283,5.549 M8.786,4.74h2.428v0.809H8.786V4.74z M16.879,15.26H3.122v-4.046h5.665v1.201c0,0.223,0.182,0.404,0.405,0.404h1.618c0.222,0,0.405-0.182,0.405-0.404v-1.201h5.665V15.26z M9.595,9.583h0.81v2.428h-0.81V9.583zM16.879,10.405h-5.665V9.19c0-0.222-0.183-0.405-0.405-0.405H9.191c-0.223,0-0.405,0.183-0.405,0.405v1.215H3.122V6.358h13.757V10.405z"
/>
</svg>
<span class="text-gray-700">Inventory</span>
</div>
<div
class="w-full flex items-center text-blue-400 h-10 pl-4 hover:bg-gray-200 rounded-lg cursor-pointer"
>
<svg class="h-6 w-6 fill-current mr-2" viewBox="0 0 20 20">
<path
d="M18.303,4.742l-1.454-1.455c-0.171-0.171-0.475-0.171-0.646,0l-3.061,3.064H2.019c-0.251,0-0.457,0.205-0.457,0.456v9.578c0,0.251,0.206,0.456,0.457,0.456h13.683c0.252,0,0.457-0.205,0.457-0.456V7.533l2.144-2.146C18.481,5.208,18.483,4.917,18.303,4.742 M15.258,15.929H2.476V7.263h9.754L9.695,9.792c-0.057,0.057-0.101,0.13-0.119,0.212L9.18,11.36h-3.98c-0.251,0-0.457,0.205-0.457,0.456c0,0.253,0.205,0.456,0.457,0.456h4.336c0.023,0,0.899,0.02,1.498-0.127c0.312-0.077,0.55-0.137,0.55-0.137c0.08-0.018,0.155-0.059,0.212-0.118l3.463-3.443V15.929z M11.241,11.156l-1.078,0.267l0.267-1.076l6.097-6.091l0.808,0.808L11.241,11.156z"
/>
</svg>
<span class="text-gray-700">Enquiries</span>
</div>
<div
class="w-full flex items-center text-blue-400 h-10 pl-4 hover:bg-gray-200 rounded-lg cursor-pointer"
>
<svg class="h-6 w-6 fill-current mr-2" viewBox="0 0 20 20">
<path
d="M16.557,4.467h-1.64v-0.82c0-0.225-0.183-0.41-0.409-0.41c-0.226,0-0.41,0.185-0.41,0.41v0.82H5.901v-0.82c0-0.225-0.185-0.41-0.41-0.41c-0.226,0-0.41,0.185-0.41,0.41v0.82H3.442c-0.904,0-1.64,0.735-1.64,1.639v9.017c0,0.904,0.736,1.64,1.64,1.64h13.114c0.904,0,1.64-0.735,1.64-1.64V6.106C18.196,5.203,17.461,4.467,16.557,4.467 M17.377,15.123c0,0.453-0.366,0.819-0.82,0.819H3.442c-0.453,0-0.82-0.366-0.82-0.819V8.976h14.754V15.123z M17.377,8.156H2.623V6.106c0-0.453,0.367-0.82,0.82-0.82h1.639v1.23c0,0.225,0.184,0.41,0.41,0.41c0.225,0,0.41-0.185,0.41-0.41v-1.23h8.196v1.23c0,0.225,0.185,0.41,0.41,0.41c0.227,0,0.409-0.185,0.409-0.41v-1.23h1.64c0.454,0,0.82,0.367,0.82,0.82V8.156z"
/>
</svg>
<span class="text-gray-700">Calender</span>
</div>
</div>
<div class="mb-4 px-4">
<p class="pl-4 text-sm font-semibold mb-1">PRODUCTS</p>
<div
class="w-full flex items-center text-blue-400 h-10 pl-4 hover:bg-gray-200 rounded-lg cursor-pointer"
>
<svg class="h-6 w-6 fill-current mr-2" viewBox="0 0 20 20">
<path
d="M14.613,10c0,0.23-0.188,0.419-0.419,0.419H10.42v3.774c0,0.23-0.189,0.42-0.42,0.42s-0.419-0.189-0.419-0.42v-3.774H5.806c-0.23,0-0.419-0.189-0.419-0.419s0.189-0.419,0.419-0.419h3.775V5.806c0-0.23,0.189-0.419,0.419-0.419s0.42,0.189,0.42,0.419v3.775h3.774C14.425,9.581,14.613,9.77,14.613,10 M17.969,10c0,4.401-3.567,7.969-7.969,7.969c-4.402,0-7.969-3.567-7.969-7.969c0-4.402,3.567-7.969,7.969-7.969C14.401,2.031,17.969,5.598,17.969,10 M17.13,10c0-3.932-3.198-7.13-7.13-7.13S2.87,6.068,2.87,10c0,3.933,3.198,7.13,7.13,7.13S17.13,13.933,17.13,10"
/>
</svg>
<span class="text-gray-700">Add Product</span>
</div>
<div
class="w-full flex items-center text-blue-400 h-10 pl-4 hover:bg-gray-200 rounded-lg cursor-pointer"
>
<svg class="h-6 w-6 fill-current mr-2" viewBox="0 0 20 20">
<path
d="M15.396,2.292H4.604c-0.212,0-0.385,0.174-0.385,0.386v14.646c0,0.212,0.173,0.385,0.385,0.385h10.792c0.211,0,0.385-0.173,0.385-0.385V2.677C15.781,2.465,15.607,2.292,15.396,2.292 M15.01,16.938H4.99v-2.698h1.609c0.156,0.449,0.586,0.771,1.089,0.771c0.638,0,1.156-0.519,1.156-1.156s-0.519-1.156-1.156-1.156c-0.503,0-0.933,0.321-1.089,0.771H4.99v-3.083h1.609c0.156,0.449,0.586,0.771,1.089,0.771c0.638,0,1.156-0.518,1.156-1.156c0-0.638-0.519-1.156-1.156-1.156c-0.503,0-0.933,0.322-1.089,0.771H4.99V6.531h1.609C6.755,6.98,7.185,7.302,7.688,7.302c0.638,0,1.156-0.519,1.156-1.156c0-0.638-0.519-1.156-1.156-1.156c-0.503,0-0.933,0.322-1.089,0.771H4.99V3.062h10.02V16.938z M7.302,13.854c0-0.212,0.173-0.386,0.385-0.386s0.385,0.174,0.385,0.386s-0.173,0.385-0.385,0.385S7.302,14.066,7.302,13.854 M7.302,10c0-0.212,0.173-0.385,0.385-0.385S8.073,9.788,8.073,10s-0.173,0.385-0.385,0.385S7.302,10.212,7.302,10 M7.302,6.146c0-0.212,0.173-0.386,0.385-0.386s0.385,0.174,0.385,0.386S7.899,6.531,7.688,6.531S7.302,6.358,7.302,6.146"
/>
</svg>
<span class="text-gray-700">View Products</span>
</div>
</div>
<div class="mb-4 px-4">
<p class="pl-4 text-sm font-semibold mb-1">MISC</p>
<div
class="w-full flex items-center text-blue-400 h-10 pl-4 hover:bg-gray-200 rounded-lg cursor-pointer"
>
<svg class="h-6 w-6 fill-current mr-2" viewBox="0 0 20 20">
<path
d="M17.592,8.936l-6.531-6.534c-0.593-0.631-0.751-0.245-0.751,0.056l0.002,2.999L5.427,9.075H2.491c-0.839,0-0.162,0.901-0.311,0.752l3.683,3.678l-3.081,3.108c-0.17,0.171-0.17,0.449,0,0.62c0.169,0.17,0.448,0.17,0.618,0l3.098-3.093l3.675,3.685c-0.099-0.099,0.773,0.474,0.773-0.296v-2.965l3.601-4.872l2.734-0.005C17.73,9.688,18.326,9.669,17.592,8.936 M3.534,9.904h1.906l4.659,4.66v1.906L3.534,9.904z M10.522,13.717L6.287,9.48l4.325-3.124l3.088,3.124L10.522,13.717z M14.335,8.845l-3.177-3.177V3.762l5.083,5.083H14.335z"
/>
</svg>
<span class="text-gray-700">Notices</span>
</div>
<div
class="w-full flex items-center text-blue-400 h-10 pl-4 hover:bg-gray-200 rounded-lg cursor-pointer"
>
<svg class="h-6 w-6 fill-current mr-2" viewBox="0 0 20 20">
<path
d="M6.176,7.241V6.78c0-0.221-0.181-0.402-0.402-0.402c-0.221,0-0.403,0.181-0.403,0.402v0.461C4.79,7.416,4.365,7.955,4.365,8.591c0,0.636,0.424,1.175,1.006,1.35v3.278c0,0.222,0.182,0.402,0.403,0.402c0.222,0,0.402-0.181,0.402-0.402V9.941c0.582-0.175,1.006-0.714,1.006-1.35C7.183,7.955,6.758,7.416,6.176,7.241 M5.774,9.195c-0.332,0-0.604-0.272-0.604-0.604c0-0.332,0.272-0.604,0.604-0.604c0.332,0,0.604,0.272,0.604,0.604C6.377,8.923,6.105,9.195,5.774,9.195 M10.402,10.058V6.78c0-0.221-0.181-0.402-0.402-0.402c-0.222,0-0.402,0.181-0.402,0.402v3.278c-0.582,0.175-1.006,0.714-1.006,1.35c0,0.637,0.424,1.175,1.006,1.351v0.461c0,0.222,0.181,0.402,0.402,0.402c0.221,0,0.402-0.181,0.402-0.402v-0.461c0.582-0.176,1.006-0.714,1.006-1.351C11.408,10.772,10.984,10.233,10.402,10.058M10,12.013c-0.333,0-0.604-0.272-0.604-0.604S9.667,10.805,10,10.805c0.332,0,0.604,0.271,0.604,0.604S10.332,12.013,10,12.013M14.629,8.448V6.78c0-0.221-0.182-0.402-0.403-0.402c-0.221,0-0.402,0.181-0.402,0.402v1.668c-0.581,0.175-1.006,0.714-1.006,1.35c0,0.636,0.425,1.176,1.006,1.351v2.07c0,0.222,0.182,0.402,0.402,0.402c0.222,0,0.403-0.181,0.403-0.402v-2.07c0.581-0.175,1.006-0.715,1.006-1.351C15.635,9.163,15.21,8.624,14.629,8.448 M14.226,10.402c-0.331,0-0.604-0.272-0.604-0.604c0-0.332,0.272-0.604,0.604-0.604c0.332,0,0.604,0.272,0.604,0.604C14.83,10.13,14.558,10.402,14.226,10.402 M17.647,3.962H2.353c-0.221,0-0.402,0.181-0.402,0.402v11.27c0,0.222,0.181,0.402,0.402,0.402h15.295c0.222,0,0.402-0.181,0.402-0.402V4.365C18.05,4.144,17.869,3.962,17.647,3.962 M17.245,15.232H2.755V4.768h14.49V15.232z"
/>
</svg>
<span class="text-gray-700">Controls</span>
</div>
<div
class="w-full flex items-center text-blue-400 h-10 pl-4 hover:bg-gray-200 rounded-lg cursor-pointer"
>
<svg class="h-6 w-6 fill-current mr-2" viewBox="0 0 20 20">
<path
d="M17.498,11.697c-0.453-0.453-0.704-1.055-0.704-1.697c0-0.642,0.251-1.244,0.704-1.697c0.069-0.071,0.15-0.141,0.257-0.22c0.127-0.097,0.181-0.262,0.137-0.417c-0.164-0.558-0.388-1.093-0.662-1.597c-0.075-0.141-0.231-0.22-0.391-0.199c-0.13,0.02-0.238,0.027-0.336,0.027c-1.325,0-2.401-1.076-2.401-2.4c0-0.099,0.008-0.207,0.027-0.336c0.021-0.158-0.059-0.316-0.199-0.391c-0.503-0.274-1.039-0.498-1.597-0.662c-0.154-0.044-0.32,0.01-0.416,0.137c-0.079,0.106-0.148,0.188-0.22,0.257C11.244,2.956,10.643,3.207,10,3.207c-0.642,0-1.244-0.25-1.697-0.704c-0.071-0.069-0.141-0.15-0.22-0.257C7.987,2.119,7.821,2.065,7.667,2.109C7.109,2.275,6.571,2.497,6.07,2.771C5.929,2.846,5.85,3.004,5.871,3.162c0.02,0.129,0.027,0.237,0.027,0.336c0,1.325-1.076,2.4-2.401,2.4c-0.098,0-0.206-0.007-0.335-0.027C3.001,5.851,2.845,5.929,2.77,6.07C2.496,6.572,2.274,7.109,2.108,7.667c-0.044,0.154,0.01,0.32,0.137,0.417c0.106,0.079,0.187,0.148,0.256,0.22c0.938,0.936,0.938,2.458,0,3.394c-0.069,0.072-0.15,0.141-0.256,0.221c-0.127,0.096-0.181,0.262-0.137,0.416c0.166,0.557,0.388,1.096,0.662,1.596c0.075,0.143,0.231,0.221,0.392,0.199c0.129-0.02,0.237-0.027,0.335-0.027c1.325,0,2.401,1.076,2.401,2.402c0,0.098-0.007,0.205-0.027,0.334C5.85,16.996,5.929,17.154,6.07,17.23c0.501,0.273,1.04,0.496,1.597,0.66c0.154,0.047,0.32-0.008,0.417-0.137c0.079-0.105,0.148-0.186,0.22-0.256c0.454-0.453,1.055-0.703,1.697-0.703c0.643,0,1.244,0.25,1.697,0.703c0.071,0.07,0.141,0.15,0.22,0.256c0.073,0.098,0.188,0.152,0.307,0.152c0.036,0,0.073-0.004,0.109-0.016c0.558-0.164,1.096-0.387,1.597-0.66c0.141-0.076,0.22-0.234,0.199-0.393c-0.02-0.129-0.027-0.236-0.027-0.334c0-1.326,1.076-2.402,2.401-2.402c0.098,0,0.206,0.008,0.336,0.027c0.159,0.021,0.315-0.057,0.391-0.199c0.274-0.5,0.496-1.039,0.662-1.596c0.044-0.154-0.01-0.32-0.137-0.416C17.648,11.838,17.567,11.77,17.498,11.697 M16.671,13.334c-0.059-0.002-0.114-0.002-0.168-0.002c-1.749,0-3.173,1.422-3.173,3.172c0,0.053,0.002,0.109,0.004,0.166c-0.312,0.158-0.64,0.295-0.976,0.406c-0.039-0.045-0.077-0.086-0.115-0.123c-0.601-0.6-1.396-0.93-2.243-0.93s-1.643,0.33-2.243,0.93c-0.039,0.037-0.077,0.078-0.116,0.123c-0.336-0.111-0.664-0.248-0.976-0.406c0.002-0.057,0.004-0.113,0.004-0.166c0-1.75-1.423-3.172-3.172-3.172c-0.054,0-0.11,0-0.168,0.002c-0.158-0.312-0.293-0.639-0.405-0.975c0.044-0.039,0.085-0.078,0.124-0.115c1.236-1.236,1.236-3.25,0-4.486C3.009,7.719,2.969,7.68,2.924,7.642c0.112-0.336,0.247-0.664,0.405-0.976C3.387,6.668,3.443,6.67,3.497,6.67c1.75,0,3.172-1.423,3.172-3.172c0-0.054-0.002-0.11-0.004-0.168c0.312-0.158,0.64-0.293,0.976-0.405C7.68,2.969,7.719,3.01,7.757,3.048c0.6,0.6,1.396,0.93,2.243,0.93s1.643-0.33,2.243-0.93c0.038-0.039,0.076-0.079,0.115-0.123c0.336,0.112,0.663,0.247,0.976,0.405c-0.002,0.058-0.004,0.114-0.004,0.168c0,1.749,1.424,3.172,3.173,3.172c0.054,0,0.109-0.002,0.168-0.004c0.158,0.312,0.293,0.64,0.405,0.976c-0.045,0.038-0.086,0.077-0.124,0.116c-0.6,0.6-0.93,1.396-0.93,2.242c0,0.847,0.33,1.645,0.93,2.244c0.038,0.037,0.079,0.076,0.124,0.115C16.964,12.695,16.829,13.021,16.671,13.334 M10,5.417c-2.528,0-4.584,2.056-4.584,4.583c0,2.529,2.056,4.584,4.584,4.584s4.584-2.055,4.584-4.584C14.584,7.472,12.528,5.417,10,5.417 M10,13.812c-2.102,0-3.812-1.709-3.812-3.812c0-2.102,1.71-3.812,3.812-3.812c2.102,0,3.812,1.71,3.812,3.812C13.812,12.104,12.102,13.812,10,13.812"
/>
</svg>
<span class="text-gray-700">Settings</span>
</div>
<router-link to="/dashboard/user">
<div
class="w-full flex items-center text-blue-400 h-10 pl-4 hover:bg-gray-200 rounded-lg cursor-pointer"
>
<FontAwesome class="h-6 w-6 fill-current mr-3" icon="users"/>
<span class="text-gray-700">Users</span>
</div>
</router-link>
</div>
</div>
</template>
<script>
import {mapState} from "vuex";
export default {
name: "Sidebar",
computed: {
...mapState(["sideBarOpen"])
}
};
</script>
+28
View File
@@ -0,0 +1,28 @@
import {library} from '@fortawesome/fontawesome-svg-core'
import {
faUsers,
faHome,
faChartBar,
faBriefcase,
faPencilAlt,
faCalendarAlt,
faPlusCircle,
faClipboardList,
faThumbtack,
faSlidersH,
faCogs,
} from '@fortawesome/free-solid-svg-icons'
library.add(
faUsers,
faHome,
faChartBar,
faBriefcase,
faPencilAlt,
faCalendarAlt,
faPlusCircle,
faClipboardList,
faThumbtack,
faSlidersH,
faCogs,
)
+294
View File
@@ -0,0 +1,294 @@
<template>
<div id="home">
<!-- breadcrumb -->
<nav class="text-sm font-semibold mb-6" aria-label="Breadcrumb">
<ol class="list-none p-0 inline-flex">
<li class="flex items-center text-blue-500">
<a href="#" class="text-gray-700">Home</a>
<svg class="fill-current w-3 h-3 mx-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"/>
</svg>
</li>
<li class="flex items-center">
<a href="#" class="text-gray-600">Dashboard</a>
</li>
</ol>
</nav>
<!-- breadcrumb end -->
<div class="lg:flex justify-between items-center mb-6">
<p class="text-2xl font-semibold mb-2 lg:mb-0">Good afternoon, Joe!</p>
<button class="bg-blue-500 hover:bg-blue-600 focus:outline-none rounded-lg px-6 py-2 text-white font-semibold shadow">
View Logs
</button>
</div>
<div class="flex flex-wrap -mx-3 mb-20">
<div class="w-1/2 xl:w-1/4 px-3">
<div class="w-full bg-white border text-blue-400 rounded-lg flex items-center p-6 mb-6 xl:mb-0">
<svg class="w-16 h-16 fill-current mr-4 hidden lg:block" viewBox="0 0 20 20">
<path d="M17.35,2.219h-5.934c-0.115,0-0.225,0.045-0.307,0.128l-8.762,8.762c-0.171,0.168-0.171,0.443,0,0.611l5.933,5.934c0.167,0.171,0.443,0.169,0.612,0l8.762-8.763c0.083-0.083,0.128-0.192,0.128-0.307V2.651C17.781,2.414,17.587,2.219,17.35,2.219M16.916,8.405l-8.332,8.332l-5.321-5.321l8.333-8.332h5.32V8.405z M13.891,4.367c-0.957,0-1.729,0.772-1.729,1.729c0,0.957,0.771,1.729,1.729,1.729s1.729-0.772,1.729-1.729C15.619,5.14,14.848,4.367,13.891,4.367 M14.502,6.708c-0.326,0.326-0.896,0.326-1.223,0c-0.338-0.342-0.338-0.882,0-1.224c0.342-0.337,0.881-0.337,1.223,0C14.84,5.826,14.84,6.366,14.502,6.708"></path>
</svg>
<div class="text-gray-700">
<p class="font-semibold text-3xl">237</p>
<p>Products Sold</p>
</div>
</div>
</div>
<div class="w-1/2 xl:w-1/4 px-3">
<div class="w-full bg-white border text-blue-400 rounded-lg flex items-center p-6 mb-6 xl:mb-0">
<svg class="w-16 h-16 fill-current mr-4 hidden lg:block" viewBox="0 0 20 20">
<path d="M17.684,7.925l-5.131-0.67L10.329,2.57c-0.131-0.275-0.527-0.275-0.658,0L7.447,7.255l-5.131,0.67C2.014,7.964,1.892,8.333,2.113,8.54l3.76,3.568L4.924,17.21c-0.056,0.297,0.261,0.525,0.533,0.379L10,15.109l4.543,2.479c0.273,0.153,0.587-0.089,0.533-0.379l-0.949-5.103l3.76-3.568C18.108,8.333,17.986,7.964,17.684,7.925 M13.481,11.723c-0.089,0.083-0.129,0.205-0.105,0.324l0.848,4.547l-4.047-2.208c-0.055-0.03-0.116-0.045-0.176-0.045s-0.122,0.015-0.176,0.045l-4.047,2.208l0.847-4.547c0.023-0.119-0.016-0.241-0.105-0.324L3.162,8.54L7.74,7.941c0.124-0.016,0.229-0.093,0.282-0.203L10,3.568l1.978,4.17c0.053,0.11,0.158,0.187,0.282,0.203l4.578,0.598L13.481,11.723z"></path>
</svg>
<div class="text-gray-700">
<p class="font-semibold text-3xl">177</p>
<p>Product Reviews</p>
</div>
</div>
</div>
<div class="w-1/2 xl:w-1/4 px-3">
<div class="w-full bg-white border text-blue-400 rounded-lg flex items-center p-6">
<svg class="w-16 h-16 fill-current mr-4 hidden lg:block" viewBox="0 0 20 20">
<path d="M14.999,8.543c0,0.229-0.188,0.417-0.416,0.417H5.417C5.187,8.959,5,8.772,5,8.543s0.188-0.417,0.417-0.417h9.167C14.812,8.126,14.999,8.314,14.999,8.543 M12.037,10.213H5.417C5.187,10.213,5,10.4,5,10.63c0,0.229,0.188,0.416,0.417,0.416h6.621c0.229,0,0.416-0.188,0.416-0.416C12.453,10.4,12.266,10.213,12.037,10.213 M14.583,6.046H5.417C5.187,6.046,5,6.233,5,6.463c0,0.229,0.188,0.417,0.417,0.417h9.167c0.229,0,0.416-0.188,0.416-0.417C14.999,6.233,14.812,6.046,14.583,6.046 M17.916,3.542v10c0,0.229-0.188,0.417-0.417,0.417H9.373l-2.829,2.796c-0.117,0.116-0.71,0.297-0.71-0.296v-2.5H2.5c-0.229,0-0.417-0.188-0.417-0.417v-10c0-0.229,0.188-0.417,0.417-0.417h15C17.729,3.126,17.916,3.313,17.916,3.542 M17.083,3.959H2.917v9.167H6.25c0.229,0,0.417,0.187,0.417,0.416v1.919l2.242-2.215c0.079-0.077,0.184-0.12,0.294-0.12h7.881V3.959z"></path>
</svg>
<div class="text-gray-700">
<p class="font-semibold text-3xl">31</p>
<p>New Enquiries</p>
</div>
</div>
</div>
<div class="w-1/2 xl:w-1/4 px-3">
<div class="w-full bg-white border text-blue-400 rounded-lg flex items-center p-6">
<svg class="w-16 h-16 fill-current mr-4 hidden lg:block" viewBox="0 0 20 20">
<path d="M17.431,2.156h-3.715c-0.228,0-0.413,0.186-0.413,0.413v6.973h-2.89V6.687c0-0.229-0.186-0.413-0.413-0.413H6.285c-0.228,0-0.413,0.184-0.413,0.413v6.388H2.569c-0.227,0-0.413,0.187-0.413,0.413v3.942c0,0.228,0.186,0.413,0.413,0.413h14.862c0.228,0,0.413-0.186,0.413-0.413V2.569C17.844,2.342,17.658,2.156,17.431,2.156 M5.872,17.019h-2.89v-3.117h2.89V17.019zM9.587,17.019h-2.89V7.1h2.89V17.019z M13.303,17.019h-2.89v-6.651h2.89V17.019z M17.019,17.019h-2.891V2.982h2.891V17.019z"></path>
</svg>
<div class="text-gray-700">
<p class="font-semibold text-3xl">1,653</p>
<p>Product Views</p>
</div>
</div>
</div>
<section class="text-gray-700 body-font">
<div class="container px-5 py-24 mx-auto">
<div class="flex flex-col text-center w-full mb-20">
<h1 class="sm:text-3xl text-2xl font-medium title-font mb-4 text-gray-900">Master Cleanse Reliac
Heirloom</h1>
<p class="lg:w-2/3 mx-auto leading-relaxed ">Whatever cardigan tote bag tumblr hexagon
brooklyn asymmetrical gentrify, subway tile poke farm-to-table. Franzen you probably haven't
heard of them man bun deep jianbing selfies heirloom prism food truck ugh squid celiac
humblebrag.</p>
</div>
<div class="flex flex-wrap -m-4 text-center">
<div class="p-4 md:w-1/4 sm:w-1/2 w-full">
<div class="border-2 border-gray-200 px-4 py-6 rounded-lg">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" class="text-indigo-500 w-12 h-12 mb-3 inline-block"
viewBox="0 0 24 24">
<path d="M8 17l4 4 4-4m-4-5v9"></path>
<path d="M20.88 18.09A5 5 0 0018 9h-1.26A8 8 0 103 16.29"></path>
</svg>
<h2 class="title-font font-medium text-3xl text-gray-900">2.7K</h2>
<p class="leading-relaxed">Downloads</p>
</div>
</div>
<div class="p-4 md:w-1/4 sm:w-1/2 w-full">
<div class="border-2 border-gray-200 px-4 py-6 rounded-lg">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" class="text-indigo-500 w-12 h-12 mb-3 inline-block"
viewBox="0 0 24 24">
<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 00-3-3.87m-4-12a4 4 0 010 7.75"></path>
</svg>
<h2 class="title-font font-medium text-3xl text-gray-900">1.3K</h2>
<p class="leading-relaxed">Users</p>
</div>
</div>
<div class="p-4 md:w-1/4 sm:w-1/2 w-full">
<div class="border-2 border-gray-200 px-4 py-6 rounded-lg">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" class="text-indigo-500 w-12 h-12 mb-3 inline-block"
viewBox="0 0 24 24">
<path d="M3 18v-6a9 9 0 0118 0v6"></path>
<path d="M21 19a2 2 0 01-2 2h-1a2 2 0 01-2-2v-3a2 2 0 012-2h3zM3 19a2 2 0 002 2h1a2 2 0 002-2v-3a2 2 0 00-2-2H3z"></path>
</svg>
<h2 class="title-font font-medium text-3xl text-gray-900">74</h2>
<p class="leading-relaxed">Files</p>
</div>
</div>
<div class="p-4 md:w-1/4 sm:w-1/2 w-full">
<div class="border-2 border-gray-200 px-4 py-6 rounded-lg">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" class="text-indigo-500 w-12 h-12 mb-3 inline-block"
viewBox="0 0 24 24">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
</svg>
<h2 class="title-font font-medium text-3xl text-gray-900">46</h2>
<p class="leading-relaxed">Places</p>
</div>
</div>
</div>
</div>
</section>
</div>
<div class="flex flex-wrap -mx-3">
<div class="w-full xl:w-1/3 px-3">
<p class="text-xl font-semibold mb-4">Recent Sales</p>
<div class="w-full bg-white border rounded-lg p-4 mb-8 xl:mb-0">
<canvas id="buyers-chart" width="600" height="400"></canvas>
</div>
</div>
<div class="w-full xl:w-1/3 px-3">
<p class="text-xl font-semibold mb-4">Recent Reviews</p>
<div class="w-full bg-white border rounded-lg p-4 mb-8 xl:mb-0">
<canvas id="reviews-chart" width="600" height="400"></canvas>
</div>
</div>
<div class="w-full xl:w-1/3 px-3">
<p class="text-xl font-semibold mb-4">Recent Transactions</p>
<div class="w-full bg-white border rounded-lg p-4">
<div class="w-full bg-gray-100 border rounded-lg flex justify-between items-center px-4 py-2 mb-4">
<div>
<p class="font-semibold text-xl">Trent Murphy</p>
<p>Product 1</p>
</div>
<span class="text-green-500 font-semibold text-lg">$25.00</span>
</div>
<div class="w-full bg-gray-100 border rounded-lg flex justify-between items-center px-4 py-2 mb-4">
<div>
<p class="font-semibold text-xl">Joseph Brent</p>
<p>Product 34</p>
</div>
<span class="text-red-500 font-semibold text-lg">$74.99</span>
</div>
<div class="w-full bg-gray-100 border rounded-lg flex justify-between items-center px-4 py-2 mb-4">
<div>
<p class="font-semibold text-xl">Jacob Bator</p>
<p>Product 23</p>
</div>
<span class="text-green-500 font-semibold text-lg">$14.95</span>
</div>
<div class="w-full bg-gray-100 border rounded-lg flex justify-between items-center px-4 py-2">
<div>
<p class="font-semibold text-xl">Alex Mason</p>
<p>Product 66</p>
</div>
<span class="text-green-500 font-semibold text-lg">$44.99</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Chart from 'chart.js'
export default {
name: 'DashboardHome',
data() {
return {
buyersData: {
type: 'line',
data: {
labels: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
datasets: [{
backgroundColor: "rgba(99,179,237,0.4)",
strokeColor: "#63b3ed",
pointColor: "#fff",
pointStrokeColor: "#63b3ed",
data: [203, 156, 99, 251, 305, 247, 256]
},
{
backgroundColor: "rgba(198,198,198,0.4)",
strokeColor: "#f7fafc",
pointColor: "#fff",
pointStrokeColor: "#f7fafc",
data: [86, 97, 144, 114, 94, 108, 156]
}]
},
options: {
legend: {
display: false
},
scales: {
yAxes: [{
gridLines: {
display: false
},
ticks: {
display: false
}
}],
xAxes: [{
gridLines: {
display: false
}
}]
}
}
},
reviewsData: {
type: 'bar',
data: {
labels: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
datasets: [{
backgroundColor: "rgba(99,179,237,0.4)",
strokeColor: "#63b3ed",
pointColor: "#fff",
pointStrokeColor: "#63b3ed",
data: [203, 156, 99, 251, 305, 247, 256]
}]
},
options: {
legend: {
display: false
},
scales: {
yAxes: [{
gridLines: {
display: false
},
ticks: {
display: false
}
}],
xAxes: [{
gridLines: {
display: false
}
}]
}
}
}
}
},
mounted() {
new Chart(document.getElementById('buyers-chart'), this.buyersData)
new Chart(document.getElementById('reviews-chart'), this.reviewsData)
}
}
</script>
+79
View File
@@ -0,0 +1,79 @@
<template>
<div id="user">
<nav class="text-sm font-semibold mb-6" aria-label="Breadcrumb">
<ol class="list-none p-0 inline-flex">
<li class="flex items-center text-blue-500">
<a href="#" class="text-gray-700">Home</a>
<svg
class="fill-current w-3 h-3 mx-3"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"
/>
</svg>
</li>
<li class="flex items-center">
<a href="#" class="text-gray-600">Users</a>
</li>
</ol>
</nav>
<div class="lg:flex justify-between items-center mb-6">
<p class="text-2xl font-semibold mb-2 lg:mb-0">Users</p>
</div>
<div class="flex flex-wrap -mx-3 mb-20">
<div class="w-full xl:w-full px-3">
<div class="w-full bg-white border rounded-lg flex items-center p-6 mb-6 xl:mb-0">
<div v-if="loading" class="loading">Loading...</div>
<datatable v-else :columns="columns" :data="rows"></datatable>
</div>
</div>
</div>
</div>
</template>
<script>
import {GetUserList} from "../store/api/users";
export default {
data: () => ({
loading: false,
columns: [
{label: "#", field: "num", headerClass: "w-1/6"},
{
label: "Name",
field: "name"
},
{
label: "Age",
field: "age"
}
],
rows: []
}),
created() {
this.fetchData();
},
watch: {
// call again the method if the route changes
$route: "fetchData"
},
methods: {
fetchData() {
this.loading = true;
GetUserList()
.then(({data}) => {
this.loading = false;
for (let i in data) {
data[i].num = parseInt(i) + 1;
}
this.rows = data;
})
.error(err => {
this.loading = false;
});
}
}
};
</script>
+116
View File
@@ -0,0 +1,116 @@
paypal.Buttons({
// Set your environment
env: paypal_env,
// Set style of buttons
style: {
layout: 'horizontal', // horizontal | vertical
size: 'large', // medium | large | responsive
shape: 'rect', // pill | rect
color: 'gold', // gold | blue | silver | black,
fundingicons: false, // true | false,
tagline: false // true | false,
},
// onInit is called when the button first renders
onInit: function (data, actions) {
// Disable the buttons
actions.disable();
// Listen for changes to the checkbox
document.querySelector('#amount')
.addEventListener('keyup', function (event) {
let amount = event.target.value;
amount = +amount;
if (isNaN(amount)) {
document.getElementById("amount-err").innerHTML = "Invalid amount format";
setTimeout(function () {
document.getElementById("amount-err").innerHTML = "";
}, 2000);
actions.disable();
} else if (amount < 5) {
document.getElementById("amount-err").innerHTML = "Amount must be greater than 5";
setTimeout(function () {
document.getElementById("amount-err").innerHTML = "";
}, 2000);
actions.disable();
}
if (event.target.value === "") {
document.getElementById("amount-err").innerHTML = "Invalid amount format";
setTimeout(function () {
document.getElementById("amount-err").innerHTML = "";
}, 2000);
actions.disable();
} else {
actions.enable();
}
});
},
// onClick is called when the button is clicked
onClick: function () {
var regex = /^\d+(?:\.\d{0,2})$/;
let amount = document.getElementById("amount").value;
amount = +amount;
if (isNaN(amount)) {
alert(2);
document.getElementById("amount-err").innerHTML = "Invalid amount format";
setTimeout(function () {
document.getElementById("amount-err").innerHTML = "";
}, 2000);
}
},
// Wait for the PayPal button to be clicked
createOrder: function () {
let formData = new FormData();
let amount = document.getElementById("amount").value;
formData.append('amount', amount);
return fetch(
'/app/paypal/do/order',
{
method: 'POST',
body: formData
}
).then(function (response) {
return response.json();
}).then(function (resJson) {
return resJson.data.id;
});
},
// Wait for the payment to be authorized by the customer
onApprove: function (data, actions) {
return fetch(
'/app/paypal/order/success/' + data.orderID,
{
method: 'POST'
}
).then(function (res) {
return res.json();
}).then(function (res) {
// window.location.href = '/account/paypal/order/success';
});
},
// Wait for the payment to be authorized by the customer
onCancel: function (data, actions) {
return fetch(
'/app/paypal/order/cancel/' + data.orderID,
{
method: 'POST'
}
).then(function (res) {
return res.json();
}).then(function (res) {
// window.location.href = '/account/paypal/order/success';
});
},
}).render('#paypalCheckoutContainer');
+16
View File
@@ -0,0 +1,16 @@
import Dashboard from './components/Dashboard'
import DashboardHome from './pages/Home'
import UserPage from './pages/User'
const routes = [
{path: '/', redirect: {name: 'DashboardHome'}},
{
path: '/dashboard', component: Dashboard, children: [
{path: '/', redirect: {name: 'DashboardHome'}},
{path: 'home', name: 'DashboardHome', component: DashboardHome},
{path: 'user', name: 'UserPage', component: UserPage}
]
}
]
export default routes
+3
View File
@@ -0,0 +1,3 @@
import Axios from "axios";
export const GetUserList = () => Axios.get("/users").then(r => r.data)
+26
View File
@@ -0,0 +1,26 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
sideBarOpen: false,
users: [],
},
getters: {
sideBarOpen: state => {
return state.sideBarOpen
}
},
mutations: {
toggleSidebar(state) {
state.sideBarOpen = !state.sideBarOpen
}
},
actions: {
toggleSidebar(context) {
context.commit('toggleSidebar')
}
}
})
View File
+3
View File
@@ -0,0 +1,3 @@
User-agent: *
Disallow:
Disallow: /cgi-bin/
+26
View File
@@ -0,0 +1,26 @@
.fade-enter-active,
.fade-leave-active {
transition-duration: 0.2s;
transition-property: opacity;
transition-timing-function: ease;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
.sidebar {
overflow-y: scroll; /* Add the ability to scroll */
}
/* Hide scrollbar for Chrome, Safari and Opera */
.sidebar::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.sidebar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
+37
View File
@@ -0,0 +1,37 @@
body {
background-image: url("../img/pattern.svg");
}
.string {
color: green;
}
.number {
color: darkorange;
}
.boolean {
color: blue;
}
.null {
color: magenta;
}
.key {
color: red;
}
.hero_button {
background-color: #0a165c !important;
color: #fff !important;
transition: all .5s ease
}
.hero_button:hover {
background-color: #6074edff !important
}
.container {
width: 70%;
}
+356
View File
@@ -0,0 +1,356 @@
@import "~flag-icons/sass/flag-icons";
.gridjs-head button,
.gridjs-footer button {
cursor: pointer;
background-color: transparent;
background-image: none;
padding: 0;
margin: 0;
border: none;
outline: none;
}
.gridjs-head {
margin-bottom: 5px;
padding: 5px 1px;
}
.gridjs-head::after {
content: "";
display: block;
clear: both;
}
.gridjs-head:empty {
padding: 0;
border: none;
}
.gridjs-container {
overflow: hidden;
display: inline-block;
padding: 2px;
color: #000;
position: relative;
z-index: 0;
}
.gridjs-footer {
display: block;
position: relative;
z-index: 0;
padding: 12px 24px;
border-top: 1px solid #e5e7eb;
background-color: transparent;
box-shadow: none;
border-radius: 0;
border-bottom-width: ipx;
border-color: #e5e7eb;
border-bottom: none;
border: 0;
}
.gridjs-footer:empty {
padding: 0;
border: none;
}
input.gridjs-input {
outline: none;
background-color: #fff;
border: 1px solid #d2d6dc;
border-radius: 5px;
padding: 10px 13px;
font-size: 14px;
line-height: 1.45;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
input.gridjs-input:focus {
box-shadow: 0 0 0 3px rgba(149, 189, 243, 0.5);
border-color: #9bc2f7;
}
.gridjs-pagination {
color: #3d4044;
}
.gridjs-pagination::after {
content: "";
display: block;
clear: both;
}
.gridjs-pagination .gridjs-summary {
float: left;
margin-top: 5px;
}
.gridjs-pagination .gridjs-pages {
float: right;
}
.gridjs-pagination .gridjs-pages button {
padding: 0.25rem 0.75rem;
border: 0;
background-color: transparent;
border-right: none;
outline: none;
margin-right: 1px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-size: 14px;
}
.gridjs-pagination .gridjs-pages button:focus {
box-shadow: none;
}
.gridjs-pagination .gridjs-pages button:hover {
background-color: #f7f7f7;
color: #3c4257;
outline: none;
}
.gridjs-pagination .gridjs-pages button:disabled,
.gridjs-pagination .gridjs-pages button[disabled],
.gridjs-pagination .gridjs-pages button:hover:disabled {
cursor: default;
background-color: transparent;
color: #6b7280;
}
.gridjs-pagination .gridjs-pages button.gridjs-spread {
cursor: default;
box-shadow: none;
background-color: transparent;
}
.gridjs-pagination .gridjs-pages button.gridjs-currentPage {
background-color: #48bb78;
color: white;
border-radius: 0.25rem;
font-weight: normal;
}
.gridjs-pagination .gridjs-pages button.gridjs-currentPage:hover {
background-color: rgba(56, 161, 105, 1);
border-radius: 0.25rem;
font-weight: normal;
color: white;
}
.gridjs-pagination .gridjs-pages button:hover {
background-color: #48bb78;
border-radius: 0.25rem;
font-weight: normal;
color: white;
}
.gridjs-pagination .gridjs-pages button:last-child {
border-bottom-right-radius: 6px;
border-top-right-radius: 6px;
border-right: 1px solid #d2d6dc;
font-size: 13px;
border: 0;
}
.gridjs-pagination .gridjs-pages button:first-child {
border-bottom-left-radius: 6px;
border-top-left-radius: 6px;
font-size: 13px;
border: 0;
}
button.gridjs-sort {
float: right;
height: 24px;
width: 16px;
background-repeat: no-repeat;
}
button.gridjs-sort-neutral {
opacity: 0.3;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAr0lEQVQ4T6WTUQ5BQQxFz1uG4IdnH2xHgo3w/xKLYSOEiMQqkCatSE2nwfy297Rz70zDn6dJ9APgAVyjvhpgAuyAOzADDiVIBDBxX0WXCFICeLENLkI8oAX2gE32WwtkChyt4AFzYJkYuwG2EeDrULMYU6AHLIBVoloDXXSFkZo4DCBnNfFU82CsD8hDPsQCiTzwkKK4BpCaQeQpS/avtd+vl6XQ0890++UzpRFKwxNZzCARuMPL3wAAAABJRU5ErkJggg==");
background-position-y: center;
}
button.gridjs-sort-asc {
padding-bottom: 6px;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAaUlEQVQ4T+3SsQ1AUBQF0GMfQ5hEoddILKHR6VT2MIZtJBoSkf/9iE68+t1T3NzMy8te5v2AZAc1Vgyxsu9KbNHtwQZ9CIkB5/CRCyIhIBSOIlegwpgYV4np+LkCOYoEMGOJAY+X/YEpbzl7DBHJyXtQAAAAAElFTkSuQmCC");
background-position-y: top;
}
button.gridjs-sort-desc {
padding-top: 6px;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAc0lEQVQ4T2NkoBAwUqifYRgaIMPAwGBAIFzOMzAwPIWpQQ+DYgYGhh4CBuQzMDBMwmUAEwMDw1QGBoYMHIaA5HIZGBj+4zIAJI7LEAzNIMW4ohHdEKya8RkAc0kvAwPDHwYGhjJkZyN7bxgmJJLzFsVhAAAclRAR/sEwnwAAAABJRU5ErkJggg==");
background-position-y: bottom;
}
button.gridjs-sort:focus {
outline: none;
}
table.gridjs-table {
max-width: 100%;
border-collapse: collapse;
text-align: left;
display: table;
margin: 0;
padding: 0;
overflow: auto;
table-layout: unset;
}
.gridjs-tbody {
background-color: #fff;
}
td.gridjs-td {
border: none;
padding: 0.5rem 1rem;
background-color: #fff;
box-sizing: content-box;
}
td.gridjs-td:first-child {
border-left: none;
}
td.gridjs-td:last-child {
border-right: none;
}
td.gridjs-message {
text-align: center;
}
th.gridjs-th {
color: hsl(220, 95%, 8%);
background-color: #f9fafb;
border: none;
padding: 0.5rem 1rem;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
box-sizing: content-box;
white-space: nowrap;
outline: none;
}
th.gridjs-th-sort {
cursor: pointer;
}
th.gridjs-th-sort:hover {
background-color: transparent;
}
th.gridjs-th-sort:focus {
background-color: transparent;
}
th.gridjs-th-fixed {
top: 0;
position: -webkit-sticky;
position: sticky;
box-shadow: 0 1px 0 0 #e5e7eb;
}
@supports (-moz-appearance: none) {
th.gridjs-th-fixed {
box-shadow: 0 0 0 1px #e5e7eb;
}
}
th.gridjs-th:first-child {
border-left: none;
}
th.gridjs-th:last-child {
border-right: none;
}
.gridjs-tr {
border: none;
border-bottom: 1px solid #e2e8f0;
}
.gridjs-tr:last-child td {
border-bottom: 0;
}
.gridjs *,
.gridjs :after,
.gridjs :before {
box-sizing: border-box;
}
.gridjs-wrapper {
position: relative;
z-index: 1;
overflow: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* box-shadow: none; */
box-shadow: 0 1px 3px 0 rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.12);
border-radius: 0.125rem;
display: block;
border-top-width: 1px;
border-color: #e5e7eb;
border-top: none;
}
.gridjs-wrapper:last-of-type {
border-radius: 0;
border-bottom-width: 1px;
}
.gridjs-search {
float: left;
}
.gridjs-search-input {
width: 250px;
}
.gridjs-loading-bar {
z-index: 10;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: #fff;
opacity: 0.5;
}
.gridjs-loading-bar::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
transform: translateX(-100%);
background-image: linear-gradient(
90deg,
rgba(204, 204, 204, 0) 0,
rgba(204, 204, 204, 0.2) 20%,
rgba(204, 204, 204, 0.5) 60%,
rgba(204, 204, 204, 0)
);
animation: shimmer 2s infinite;
content: "";
}
@keyframes shimmer {
100% {
transform: translateX(100%);
}
}
+96
View File
@@ -0,0 +1,96 @@
@import url("https://fonts.googleapis.com/css2?family=Raleway:wght@100;300;400;500;600;700;800;900&display=swap");
@tailwind base;
@tailwind components;
.bg-accent {
background-color: #f3f5f9;
}
.bg-primary {
background-color: #006aff;
}
.transition-basic {
@apply transition-colors duration-200 ease-in;
}
.shadow-primary {
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14),
0 2px 1px -1px rgba(0, 0, 0, 0.12);
}
.shadow-dropdown {
box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.3), 0 2px 4px 1px rgba(0, 0, 0, 0.15);
}
@tailwind utilities;
.fade-enter-active,
.fade-leave-active {
transition-duration: 0.2s;
transition-property: opacity;
transition-timing-function: ease;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
/* used for dropdown */
.slide-in-up-enter-active,
.slide-in-up-leave-active {
transition-duration: 250ms;
transition-property: transform, visibility, opacity;
transition-timing-function: ease-in;
transform: translate3d(0, 0, 0);
}
.slide-in-up-enter,
.slide-in-up-leave-active {
transform: translate3d(0, 50%, 0);
opacity: 0;
}
/* bounce in up */
.bounce-in-up-enter-active {
animation: bounceInUp 1s;
}
/* .bounce-in-up-leave-active { */
/* animation: bounceInUp 0.5s reverse; */
/* } */
@keyframes bounceInUp {
from,
60%,
75%,
90%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
from {
opacity: 0;
transform: translate3d(0, 3000px, 0) scaleY(5);
}
60% {
opacity: 1;
transform: translate3d(0, -20px, 0) scaleY(0.9);
}
75% {
transform: translate3d(0, 10px, 0) scaleY(0.95);
}
90% {
transform: translate3d(0, -5px, 0) scaleY(0.985);
}
to {
transform: translate3d(0, 0, 0);
}
}
@@ -0,0 +1,477 @@
<div x-data="app" class="bg-white rounded-lg shadow">
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<div class="container mx-auto p-6 w-full">
<div class="justify-between items-center w-full md:flex">
<div>
<h1 class="text-2xl font-bold mb-2">Lista de actualizaciones de datos de usuario</h1>
<p class="mb-4 text-sm">Gestión de actualizaciones de datos de usuario.</p>
</div>
<div class="mb-4 md:mb-0 relative">
<input type="text" placeholder="Buscar..." class="border border-gray-300 rounded p-2 w-full"
x-model="search" @input.debounce.500ms="loadData()" />
</div>
<div class="flex itmems-center justify-end mb-4 md:mb-0">
<button @click="exportToExcel()" class="bg-[#8eb02f] text-white px-4 py-2 rounded text-sm">Exportar a
Excel</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="table-auto w-full">
<thead class="text-sm text-left py-4 border-b border-gray-300">
<tr class="text-left font-semibold border-collapse">
<th class="py-2 px-4 border-b">Código</th>
<th class="py-2 px-4 border-b">Nombre</th>
<th class="py-2 px-4 border-b">Tipo persona</th>
<th class="py-2 px-4 border-b">Celular</th>
<th class="py-2 px-4 border-b">Celular alternativo</th>
<th class="py-2 px-4 border-b">Correo</th>
<th class="py-2 px-4 border-b">Dirección</th>
<th class="py-2 px-4 border-b">Tipo documento</th>
<th class="py-2 px-4 border-b">N° documento</th>
<th class="py-2 px-4 border-b">OTP</th>
<th class="py-2 px-4 border-b">Revisado</th>
<th class="py-2 px-4 border-b">Fecha registro</th>
<th class="py-2 px-4 border-b"></th>
</tr>
</thead>
<tbody class="text-gray-500 select-none text-sm">
<template x-for="data in datos" :key="data.id">
<tr class="hover:bg-gray-100">
<td class="py-3 text-xs px-4 border-b" x-text="data.codigo"></td>
<td class="py-3 text-xs px-4 border-b" x-text="data.name"></td>
<td class="py-3 text-xs px-4 border-b" x-text="data.tipo_persona"></td>
<td class="py-3 text-xs px-4 border-b" x-text="data.movil1"></td>
<td class="py-3 text-xs px-4 border-b" x-text="data.movil2"></td>
<td class="py-3 text-xs px-4 border-b" x-text="data.email"></td>
<td class="py-3 text-xs px-4 border-b" x-text="data.direccion"></td>
<td class="py-3 text-xs px-4 border-b" x-text="data.tipo_documento"></td>
<td class="py-3 text-xs px-4 border-b" x-text="data.documento"></td>
<td class="py-3 text-xs px-4 border-b">
<div class="flex items-center">
<div :class="data.validacion_otp ? 'bg-green-500' : 'bg-gray-400'"
class="w-3 h-3 rounded-full mr-2"></div>
<span x-text="data.validacion_otp ? 'Validado' : 'No Validado'"></span>
</div>
</td>
<td class="py-3 text-xs px-4 border-b">
<div class="flex items-center">
<div :class="data.revisado ? 'bg-green-500' : 'bg-gray-400'"
class="w-3 h-3 rounded-full mr-2"></div>
<span x-text="data.revisado ? 'Revisado' : 'No Revisado'"></span>
</div>
</td>
<td class="py-3 text-xs px-4 border-b"
x-text="data.created_at ? new Date(data.created_at).toLocaleDateString('es-ES', { year: 'numeric', month: 'long', day: 'numeric' }) : 'Fecha no disponible'">
</td>
<td class="py-3 text-xs px-4 border-b">
<div class="flex items-center gap-2">
<button @click="openAddModal(data)" title="Añadir nota">
<svg class="w-5 text-gray-500" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12H14M12 10V14M19.9592 15H16.6C16.0399 15 15.7599 15 15.546 15.109C15.3578 15.2049 15.2049 15.3578 15.109 15.546C15 15.7599 15 16.0399 15 16.6V19.9592M20 14.1031V7.2C20 6.07989 20 5.51984 19.782 5.09202C19.5903 4.71569 19.2843 4.40973 18.908 4.21799C18.4802 4 17.9201 4 16.8 4H7.2C6.0799 4 5.51984 4 5.09202 4.21799C4.71569 4.40973 4.40973 4.71569 4.21799 5.09202C4 5.51984 4 6.0799 4 7.2V16.8C4 17.9201 4 18.4802 4.21799 18.908C4.40973 19.2843 4.71569 19.5903 5.09202 19.782C5.51984 20 6.0799 20 7.2 20H14.1031C14.5923 20 14.8369 20 15.067 19.9447C15.2711 19.8957 15.4662 19.8149 15.6451 19.7053C15.847 19.5816 16.0199 19.4086 16.3658 19.0627L19.0627 16.3658C19.4086 16.0199 19.5816 15.847 19.7053 15.6451C19.8149 15.4662 19.8957 15.2711 19.9447 15.067C20 14.8369 20 14.5923 20 14.1031Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</button>
<button @click="openViewModal(data)" title="Ver">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 text-[#8eb02f]">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<!--button @click="openEditModal(data)" title="Editar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</button>
<button @click="openDeleteModal(data)" title="Eliminar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 text-red-500">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button-->
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="flex justify-start items-center gap-2 p-4 text-sm">
<button @click="page = Math.max(1, page - 1); loadData()" :disabled="page === 1"
class="px-4 py-2 text-black bg-gray-200 rounded-l-md opacity-50 disabled:cursor-not-allowed">
Anterior
</button>
<template x-for="pageNum in paginatedPages" :key="pageNum">
<button @click="goToPage(pageNum)" class="px-4 py-2 text-black bg-gray-200 bg-black text-gray-50 rounded-md hover:bg-gray-300"
:class="{ 'bg-black text-white': pageNum === page }" x-text="pageNum"></button>
</template>
<button @click="page += 1; loadData()" :disabled="datos.length < limit" :disabled="page === totalPages"
class="px-4 py-2 text-black bg-gray-200 rounded-r-md opacity-50 disabled:cursor-not-allowed">
Siguiente
</button>
</div>
</div>
<!-- Modales -->
<div x-show="viewModal" x-transition:enter="transition ease-out duration-300 transform" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-200 transform" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
class="fixed inset-0 overflow-auto bg-gray-800 bg-opacity-75 flex justify-center items-center text-sm z-50">
<div class="bg-white rounded-lg shadow-lg p-6 w-11/12 max-w-lg">
<h2 class="text-xl font-bold mb-4">Ver actualizacion de datos de usuario</h2>
<div class="w-full mb-4">
<label class="block mb-2">Tipo de usuario:</label>
<input type="text" x-model="selectedItem.tipo_usuario" disabled
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el tipo de usuario" />
</div>
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
<div class="w-full">
<label class="block mb-2">Tipo de persona:</label>
<input type="text" x-model="selectedItem.tipo_persona" disabled
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el tipo de persona" />
</div>
<div class="w-full">
<label class="block mb-2">Nombre:</label>
<input type="text" x-model="selectedItem.name" disabled
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el nombre" />
</div>
</div>
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
<div class="w-full">
<label class="block mb-2">Tipo de documento:</label>
<input type="email" x-model="selectedItem.tipo_documento" disabled
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el email" />
</div>
<div class="w-full">
<label class="block mb-2">Documento:</label>
<input type="email" x-model="selectedItem.documento" disabled
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el email" />
</div>
</div>
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
<div class="w-full">
<label class="block mb-2">Celular:</label>
<input type="email" x-model="selectedItem.movil1" disabled
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el celular" />
</div>
<div class="w-full">
<label class="block mb-2">Celular alternativo:</label>
<input type="email" x-model="selectedItem.movil2" disabled
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el celular alternativo" />
</div>
</div>
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
<div class="w-full">
<label class="block mb-2">Email:</label>
<input type="email" x-model="selectedItem.email" disabled
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el email" />
</div>
<div class="w-full">
<label class="block mb-2">Dirección:</label>
<input type="email" x-model="selectedItem.direccion" disabled
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el direccion" />
</div>
</div>
<div class="flex justify-between gap-2 text-sm">
<button @click="addModal = true" class="px-4 py-2 bg-[#8eb02f] text-white rounded">Añadir nota</button>
<button @click="viewModal = false" class="px-4 py-2 bg-gray-600 text-white rounded">Cerrar</button>
</div>
</div>
</div>
<div x-show="editModal" x-transition:enter="transition ease-out duration-300 transform" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-200 transform" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
class="fixed inset-0 overflow-auto bg-gray-800 bg-opacity-75 flex justify-center items-center text-sm z-50">
<div class="bg-white rounded-lg shadow-lg p-6 w-11/12 max-w-lg">
<h2 class="text-xl font-bold mb-4">Editar actualizacion de datos de usuario</h2>
<div class="w-full mb-4">
<label class="block mb-2">Tipo de usuario:</label>
<input type="text" x-model="selectedItem.tipo_usuario"
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el tipo de usuario" />
</div>
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
<div class="w-full">
<label class="block mb-2">Tipo de persona:</label>
<input type="text" x-model="selectedItem.tipo_persona"
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el tipo de persona" />
</div>
<div class="w-full">
<label class="block mb-2">Nombre:</label>
<input type="text" x-model="selectedItem.name"
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el nombre" />
<p x-show="errors.name" x-text="errors.name" class="text-red-500 text-sm"></p>
</div>
</div>
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
<div class="w-full">
<label class="block mb-2">Tipo de documento:</label>
<input type="email" x-model="selectedItem.tipo_documento"
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el email" />
<p x-show="errors.tipo_documento" x-text="errors.tipo_documento" class="text-red-500 text-sm"></p>
</div>
<div class="w-full">
<label class="block mb-2">Documento:</label>
<input type="email" x-model="selectedItem.documento"
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el email" />
<p x-show="errors.documento" x-text="errors.documento" class="text-red-500 text-sm"></p>
</div>
</div>
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
<div class="w-full">
<label class="block mb-2">Celular:</label>
<input type="email" x-model="selectedItem.movil1"
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el celular" />
</div>
<div class="w-full">
<label class="block mb-2">Celular alternativo:</label>
<input type="email" x-model="selectedItem.movil2"
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el celular alternativo" />
</div>
</div>
<div class="flex flex-col sm:flex-row justify-between gap-4 mb-4">
<div class="w-full">
<label class="block mb-2">Email:</label>
<input type="email" x-model="selectedItem.email"
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el email" />
</div>
<div class="w-full">
<label class="block mb-2">Dirección:</label>
<input type="email" x-model="selectedItem.direccion"
class="border border-gray-300 rounded p-2 w-full" placeholder="Ingresa el direccion" />
</div>
</div>
<div class="flex justify-between gap-2 text-sm">
<button @click="updateItem()" class="px-4 py-2 bg-[#8eb02f] text-white rounded">Actualizar</button>
<button @click="closeModals()" class="px-4 py-2 bg-gray-600 text-white rounded">Cerrar</button>
</div>
</div>
</div>
<div x-show="addModal" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex justify-center items-center text-sm z-50">
<div class="bg-white rounded-lg shadow-lg p-6 w-11/12 max-w-lg">
<h2 class="text-xl font-bold mb-4">Añadir nota al cliente</h2>
<div class="mb-4">
<label class="block mb-2">Notas:</label>
<textarea x-model="item.notas" class="border border-gray-300 rounded p-2 w-full"
placeholder="Ingresa la nota"></textarea>
</div>
<div class="flex justify-between gap-2 text-sm">
<button @click="addNote()" class="px-4 py-2 bg-[#8eb02f] text-white rounded">Añadir nota</button>
<button @click="addModal = false" class="px-4 py-2 bg-gray-600 text-white rounded">Cancelar</button>
</div>
</div>
</div>
<div x-show="deleteModal" class="fixed inset-0 overflow-auto bg-gray-800 bg-opacity-75 flex justify-center items-center z-50">
<div class="bg-white rounded-lg shadow-lg p-6 w-11/12 max-w-lg">
<h2 class="text-xl font-bold mb-4">Confirmar Eliminación</h2>
<p>¿Estás seguro de que deseas eliminar este registro?</p>
<div class="flex mt-4 justify-end gap-2 text-sm">
<button @click="deleteItem()" class="px-4 py-2 bg-red-500 text-white rounded">Eliminar</button>
<button @click="closeModals()" class="bg-gray-600 text-white px-4 py-2 rounded">Cancelar</button>
</div>
</div>
</div>
</div>
<script>
function app() {
return {
datos: [],
loading: false,
limit: 10,
page: 1,
search: '',
totalPages: 0,
paginatedPages: [],
// modales
viewModal: false,
addModal: false,
editModal: false,
deleteModal: false,
// inputs
item: {
id: '', notas: ''
},
selectedItem: {},
errors: {},
loadData() {
if (this.search !== '') {
this.page = 1;
this.limit = 100;
} else {
this.limit = 10;
}
this.loading = true;
axios.get('/app/loadactualizaciondatosusuarios', {
params: {
page: this.page,
limit: this.limit,
search: this.search
}
}).then(response => {
this.datos = response.data.registros.map(data => ({
id: data.ID,
tipo_persona: data.tipo_persona,
name: data.name,
movil1: data.movil1,
movil2: data.movil2,
email: data.email,
codigo: data.codigo,
tipo_usuario: data.tipo_usuario,
direccion: data.direccion,
tipo_documento: data.tipo_documento,
documento: data.documento,
notas: data.notas,
revisado: data.revisado,
created_at: data.CreatedAt,
validacion_otp: data.validacion_otp
}));
this.totalPages = response.data.totalPages;
this.calculatePaginatedPages();
}).catch(error => {
console.error('Error al obtener datos:', error);
}).finally(() => {
this.loading = false;
});
},
setSelectedItem(data) {
this.selectedItem = { ...data };
this.errors = {};
},
resetSelectedItem() {
this.selectedItem = {};
this.errors = {};
},
calculatePaginatedPages() {
const maxVisiblePages = 5;
const startPage = Math.max(1, this.page - Math.floor(maxVisiblePages / 2));
const endPage = Math.min(this.totalPages, startPage + maxVisiblePages - 1);
this.paginatedPages = Array.from({ length: endPage - startPage + 1 }, (_, i) => i + startPage);
},
goToPage(page) {
this.page = page;
this.loadData();
},
setLoading(value) {
this.loading = value;
},
validateFields(fields) {
this.errors = {};
fields.forEach(field => {
if (!this.selectedItem[field]) {
this.errors[field] = 'Este campo es obligatorio';
}
});
return Object.keys(this.errors).length === 0;
},
init() {
this.loadData();
},
openViewModal(data) {
this.setSelectedItem(data);
this.viewModal = true;
},
openEditModal(data) {
this.setSelectedItem(data);
this.editModal = true;
},
updateItem() {
if (this.validateFields(['tipo_documento', 'documento', 'name'])) {
this.setLoading(true);
axios.put(`/app/actualizacion-datos-usuario/${this.selectedItem.id}`, this.selectedItem)
.then(() => {
alert("Registro actualizado exitosamente.");
this.resetSelectedItem();
this.loadData();
})
.catch(error => console.error("Error al actualizar el registro:", error))
.finally(() => {
this.closeModals()
this.setLoading(false);
});
}
},
deleteItem() {
this.setLoading(true);
axios.delete(`/app/actualizacion-datos-usuario/${this.selectedItem.id}`)
.then(() => {
alert("Registro eliminado exitosamente.");
this.resetSelectedItem();
this.loadData();
})
.catch(error => console.error("Error al eliminar el registro:", error))
.finally(() => this.setLoading(false));
},
openAddModal(data) {
this.selectedItem.id = data.id;
this.addModal = true;
},
openDeleteModal(data) {
this.selectedItem.id = data.id;
this.deleteModal = true;
},
closeModals(){
this.viewModal = false;
this.addModal = false;
this.editModal = false;
this.deleteModal = false;
},
addNote() {
if (this.selectedItem.id && this.item.notas) {
axios.put(`/app/actualizacion-datos-usuario/${this.selectedItem.id}`, {
notas: this.item.notas,
revisado: true
}).then(() => {
alert("Nota añadida exitosamente.");
this.item.notas = '';
this.addModal = false;
this.loadData();
}).catch(error => console.error("Error al añadir la nota:", error));
} else {
alert('Debe seleccionar un usuario y añadir una nota');
}
},
exportToExcel() {
axios.get('/app/export/actualizacion-datos-usuarios', { responseType: 'blob' })
.then(response => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'actualizacion_datos_usuarios.xlsx');
document.body.appendChild(link);
link.click();
})
.catch(error => console.error('Error al exportar datos:', error));
}
}
}
</script>
+88
View File
@@ -0,0 +1,88 @@
<div class="max-w-5xl mx-auto p-8">
<div class="section-title flex items-center mb-6">
<span class="w-1 h-10 bg-blue-800 mr-4"></span>
<h2 class="text-3xl font-bold text-gray-800">
Oficina Virtual Gases del Oriente
</h2>
</div>
<p class="mb-12">
Bienvenid@ a la Oficina Virtual de Gases del Oriente SA ESP. Si ya está registrado(a), puede
iniciar sesión con el Usuario y Contraseña asignados durante el registro. Si aún no tiene Usuario y Contraseña,
es necesario registrarse según las acciones que desee realizar.
</p>
<div class="relative w-full max-w-5xl bg-white shadow-lg rounded-[35px] overflow-hidden">
<div class="relative w-full h-0 pb-[120%] md:pb-[60%] lg:pb-[50%]"> <!-- Aspect ratio box -->
<img src="/img/img20.webp" alt="Background Image"
class="absolute inset-0 w-full h-full object-cover rounded-[35px]">
<div class="absolute inset-0 z-10 flex flex-col justify-center p-8 lg:max-w-md lg:ml-8">
<h1 class="text-2xl lg:text-4xl font-bold mb-6 text-[#404e7b] text-center">Iniciar sesión</h1>
<form action="/do/login" method="POST">
<div class="mb-4">
<label for="email" class="block text-md font-medium text-gray-700">Correo electrónico</label>
<input type="email" id="email" required name="email"
class="mt-1 block w-full px-3 py-3 lg:py-4 rounded-xl shadow-[0_5px_25px_rgba(0,0,0,0.1)]"
placeholder="Ingrese su correo electrónico" />
</div>
<div class="mb-8">
<label for="password" class="block text-md font-medium text-gray-700">Contraseña</label>
<input type="password" id="password" name="password" required
class="mt-1 block w-full px-3 py-3 lg:py-4 rounded-xl shadow-[0_5px_25px_rgba(0,0,0,0.1)]"
placeholder="Ingrese su contraseña" />
</div>
<a href="/request-password-reset" class="text-black">¿Olvidaste tu contraseña?</a>
<div class="flex justify-center">
<button type="{{.Type}}" class="primary-button py-2 px-5 bg-[#284c91] text-white font-medium rounded shadow-sm">
Iniciar sesión
</button>
{{if .error}}
<p class="text-red-300">{{.message}}</p>
{{end}}
</div>
</form>
</div>
</div>
</div>
</div>
<div class="max-w-5xl mx-auto p-8 mb-8">
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="flex flex-col">
<div class="flex items-center mb-4">
<div class="w-1 h-16 bg-[#274e93] mr-2"></div>
<h2 class="text-3xl font-bold text-gray-800">Registrarse Como Usuario del Gas Natural</h2>
</div>
<div class="flex-grow bg-white p-10 rounded-lg shadow-[0_5px_25px_rgba(0,0,0,0.1)]">
<p class="mb-4 text-gray-600">
Podrá Ver o Imprimir facturas, Consultar o Registrar PQRs, Consultar Estado de créditos, Realizar
pagos.
</p>
<a href="/register-gas">
<button type="{{.Type}}" class="secondary-button w-full py-2 px-5 bg-[#beda75] text-[#284c91] font-medium rounded shadow-sm hover:bg-[#aecc5b] my-auto">
Registrarse como usuario del Gas
</button>
</a>
</div>
</div>
<div class="flex flex-col">
<div class="flex items-center mb-4">
<div class="w-1 h-16 bg-[#274e93] mr-2"></div>
<h2 class="text-3xl font-bold text-gray-800">Registrarse Como Organismo de Inspección (OIA)</h2>
</div>
<div class="flex-grow bg-white p-10 rounded-lg shadow-[0_5px_25px_rgba(0,0,0,0.1)]">
<p class="mb-4 text-gray-600">
Para cargar los certificados de resultados de inspección de conformidad.
</p>
<a href="/register-oia">
<button type="{{.Type}}" class="secondary-button w-full py-2 px-5 bg-[#beda75] text-[#284c91] font-medium rounded shadow-sm hover:bg-[#aecc5b] my-auto">
Registrarse como OIA
</button>
</a>
</div>
</div>
</div>
</div>
+50
View File
@@ -0,0 +1,50 @@
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="w-full max-w-md bg-white rounded-lg shadow-lg p-6">
<h1 class="text-2xl font-bold text-center text-gray-700 mb-6">¡Restablece tu contraseña!</h1>
<form action="/do/password-reset/{{.Token}}" method="POST" class="space-y-4">
<div>
<label for="email" class="block text-sm font-medium text-gray-700">Codigo de Usuario</label>
<input
id="email"
type="text"
placeholder="Ingresa tu codigo de usuario"
name="nombre_usuario"
required
class="w-full mt-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500 focus:border-blue-500"
>
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700">Nueva contraseña</label>
<input
id="password"
type="password"
placeholder="Ingresa tu nueva contraseña"
name="password"
required
class="w-full mt-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500 focus:border-blue-500"
>
</div>
<div>
<label for="c_password" class="block text-sm font-medium text-gray-700">Confirma tu contraseña</label>
<input
id="c_password"
type="password"
placeholder="Confirma tu nueva contraseña"
name="c_password"
required
class="w-full mt-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500 focus:border-blue-500"
>
</div>
<button
type="submit"
class="w-full bg-blue-500 text-white font-bold py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring focus:ring-blue-300"
>
Restablecer contraseña
</button>
</form>
</div>
</div>
+50
View File
@@ -0,0 +1,50 @@
<div class="p-10">
<div class="bg-cover h-full">
<div class="content px-8 py-2">
<div class="body mt-10 mx-8">
<div class="md:flex items-center justify-between">
<div class="w-full md:w-1/2 mr-auto">
</div>
<div class="w-full md:max-w-md mt-6">
<div class="card bg-white shadow-md rounded-lg px-4 py-4 mb-6 ">
<form action="/do/register" method="POST">
<div class="flex items-center justify-center">
<h2 class="text-2xl font-bold tracking-wide">
Let me in!!!
</h2>
</div>
<h2 class="text-xl text-center font-semibold text-gray-800 mb-2">
Sign Up
</h2>
<div class="flex items-center justify-center">
{{if .error}}
<p class="text-red-300">{{.message}}</p>
{{end}}
</div>
<input name="name" required type="text"
class="rounded px-4 w-full py-1 bg-gray-200 border border-gray-400 mb-6 text-gray-700 placeholder-gray-700 focus:bg-white focus:outline-none"
placeholder="Full Name">
<input name="email" required type="email"
class="rounded px-4 w-full py-1 bg-gray-200 border border-gray-400 mb-6 text-gray-700 placeholder-gray-700 focus:bg-white focus:outline-none"
placeholder="Email Address">
<input name="password" required type="password"
class="rounded px-4 w-full py-1 bg-gray-200 border border-gray-400 mb-4 text-gray-700 placeholder-gray-700 focus:bg-white focus:outline-none"
placeholder="Password">
<input name="c_password" required type="password"
class="rounded px-4 w-full py-1 bg-gray-200 border border-gray-400 mb-4 text-gray-700 placeholder-gray-700 focus:bg-white focus:outline-none"
placeholder="Confirm Password">
<div class="flex items-center justify-between">
<button class="bg-black text-gray-200 px-2 py-1 rounded" type="submit">Let me in!
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,11 @@
<h1 class="h1">Reset my password!</h1>
<form action="/do/reset-password" method="POST">
<div class="container">
<label for="Usuario"><b>Codigo de usuario</b></label>
<input id="Usuario" type="text" placeholder="Ingrese su usuario" name="email" required>
<button type="submit">Let me reset password!!</button>
</div>
</form>
{{.Login.Email}}
+22
View File
@@ -0,0 +1,22 @@
<!-- Container Principal -->
<div x-data="modals" @keydown.escape="closeModal" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full ">
<h1 class="text-2xl font-bold mb-4">Oficina Virtual</h1>
<div class="justify-between w-full md:flex">
<p class="mb-4 text-sm">Bienvenido a la oficina virtual de GASES DEL ORIENTE S.A E.S.P</p>
<a href="/"><button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded flex space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
</svg>
<p> Volver a la pagina principal </p>
</button>
</a>
<!-- Agregar el GIF de carga -->
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
</div>
+450
View File
@@ -0,0 +1,450 @@
<!-- Container Principal -->
<div x-data="modals" @keydown.escape="closeModal" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full">
<h1 class="text-2xl font-bold mb-4">Lista de deberes y derechos de usuario</h1>
<div class="justify-between w-full md:flex">
<p class="mb-4 text-sm">Gestión de deberes y derechos de usuario</p>
<!-- Campo de Búsqueda -->
<div class="mb-4 relative">
<input type="text" x-model="searchQuery" placeholder="Buscar..."
class="border border-gray-300 rounded w-full md:w-60 p-2 text-sm" @input="loadRegister()">
<div x-show="isLoading" class="absolute right-2 top-2 flex items-center">
<svg class="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
</svg>
</div>
</div>
<div class="flex justify-end mb-4">
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded text-sm"
x-on:click="addModal=true">Nuevo</button>
</div>
</div>
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<div class="overflow-x-auto max-w-full">
<!-- Tabla de Registros -->
<table class="table-auto w-full">
<thead class="text-sm text-left py-4 border-b border-gray-300">
<tr class="text-left font-semibold border-collapse">
<th class="py-2 px-4 border-b">Item</th>
<th class="py-2 px-4 border-b">Título</th>
<th class="py-2 px-4 border-b">Descripción</th>
<th class="py-2 px-4 border-b">Imagen</th>
<th class="py-2 px-4 border-b">Archivo</th>
<th class="py-2 px-4 border-b">Botón de Texto</th>
<th class="py-2 px-4 border-b w-32"></th>
</tr>
</thead>
<tbody class="text-gray-500 select-none text-sm">
<!-- Mensaje de Carga Inicial -->
<tr class="hover:bg-gray-100" x-show="isLoading">
<td colspan="" class="py-2 px-4 border-b text-center w-full">
<div class="text-center mx-auto justify-center flex space-x-2">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 mx-1 text-gray-500" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z">
</path>
</svg>
Cargando registros...
</div>
</td>
</tr>
<template x-for="registro in registros" :key="registro.ID">
<tr class="hover:bg-gray-100" :data-role-id="registro.ID">
<td class="py-3 text-xs px-4 border-b" x-text="registro.ID"></td>
<td class="py-3 text-xs px-4 border-b" x-text="registro.title"></td>
<td class="py-3 text-xs px-4 border-b" x-text="registro.description"></td>
<td class="py-3 text-xs px-4 border-b">
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="window.open(`${window.location.origin}/${registro.imagen}`, '_blank')">
Ver imagen
</button>
</td>
<td class="py-3 text-xs px-4 border-b">
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="window.open(`${window.location.origin}/${registro.archivo}`, '_blank')">
Ver archivo
</button>
</td>
<td class="py-3 text-xs px-4 border-b" x-text="registro.text_button"></td>
<td class="py-3 text-xs px-4 border-b">
<div class="flex justify-between gap-2">
<button
@click="viewModal = true; Title = registro.title; Description = registro.description; Imagen = registro.imagen; Archivo = registro.archivo; Text_button = registro.text_button;"
title="Ver">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-[#8eb02f]">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<button
@click="editModal = true; Id = registro.ID; Title = registro.title; Description = registro.description; Imagen = registro.imagen; Archivo = registro.archivo; Text_button = registro.text_button;"
title="Editar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</button>
<button @click="showConfirmModal = true; deleteId = registro.ID;" title="Eliminar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-red-500">
<path stroke-linecap="round" stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button>
</div>
</td>
</tr>
</template>
<tr x-show="sinregistro">
<td colspan="7" class="py-2 px-4 border-b text-center">No hay registros disponibles.</td>
</tr>
</tbody>
</table>
</div>
<div>
<div id="pagination" class="flex justify-left my-4 text-sm"></div>
<div id="recordCountDiv" class="text-sm text-gray-700 ml-4"></div>
</div>
</div>
<!-- Modal Ver -->
<div x-cloak x-show="viewModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Ver deberes y derechos de usuario</h2>
<p class="font-semibold" x-text="Title"></p>
<p class="mt-2" x-text="Description"></p>
<p class="mt-2" x-text="Text_button"></p>
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="window.open(`${window.location.origin}/${Imagen}`, '_blank')">
Ver imagen
</button>
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="window.open(`${window.location.origin}/${Archivo}`, '_blank')">
Ver archivo
</button>
<button class="mt-4 bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="viewModal = false">Cerrar</button>
</div>
</div>
<!-- Modal Editar -->
<div x-cloak x-show="editModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Editar deberes y derechos de usuario</h2>
<form @submit.prevent="submitEdit">
<label class="block mt-4 mb-2" for="editTitle">Título</label>
<input id="editTitle" x-model="Title" class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="editDescription">Descripción</label>
<textarea id="editDescription" x-model="Description" class="border border-gray-300 rounded w-full p-2"
required></textarea>
<label class="block mt-4 mb-2" for="editImagen">Imagen</label>
<input type="file" id="editImagen" @change="handleUpdateFileChange($event, 'imagen')"
class="border border-gray-300 rounded w-full p-2" accept="image/*">
<label class="block mb-2" for="editArchivo">Archivo</label>
<input type="file" id="editArchivo" @change="handleUpdateFileChange($event, 'archivo')"
class="border border-gray-300 rounded w-full p-2" accept=".jpg,.png,.pdf,.doc,.docx">
<label class="block mt-4 mb-2">Botón de Texto</label>
<input type="text" x-model="Text_button" class="border border-gray-300 rounded w-full p-2" required>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar Cambios</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="editModal = false">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Añadir -->
<div x-cloak x-show="addModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Agregar deberes y derechos de usuario</h2>
<form @submit.prevent="submitAdd">
<label class="block mt-4 mb-2" for="newTitle">Título</label>
<input id="title" x-model="newTitle" class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="newDescription">Descripción</label>
<textarea id="description" x-model="newDescription" class="border border-gray-300 rounded w-full p-2"
required></textarea>
<label class="block mt-4 mb-2" for="newImagen">Imagen</label>
<input type="file" id="imagen" @change="handleFileChange($event, 'imagen')"
class="border border-gray-300 rounded w-full p-2" accept="image/*" required>
<label class="block mb-2" for="newArchivo">Archivo</label>
<input type="file" id="archivo" @change="handleFileChange($event, 'archivo')"
class="border border-gray-300 rounded w-full p-2" accept=".jpg,.png,.pdf,.doc,.docx" required>
<label class="block mt-4 mb-2">Botón de Texto</label>
<input type="text" id="text_button" x-model="newText_button"
class="border border-gray-300 rounded w-full p-2" required>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar Publicación</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Confirmar Eliminación -->
<div x-cloak x-show="showConfirmModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-4">Confirmar Eliminación</h2>
<p>¿Estás seguro de que deseas eliminar esta registro?</p>
<div class="mt-4 flex justify-end">
<button class="bg-red-500 text-white px-4 py-2 rounded mr-2" @click="deleteItem()">Eliminar</button>
<button @click="closeModal" class="bg-gray-600 text-white px-4 py-2 rounded">Cancelar</button>
</div>
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('modals', () => ({
addModal: false,
editModal: false,
viewModal: false,
showConfirmModal: false,
newTitle: '',
newDescription: '',
newImagen: null, // Imagen para la carga
newArchivo: null, // Archivo para la carga
newText_button: '', // Botón de texto (como texto)
Title: '',
Description: '',
Imagen: '',
Archivo: '',
Text_button: '', // Botón de texto (como texto)
updateImagen: null, // Imagen para la actualización
updateArchivo: null, // Archivo para la actualización
sinregistro: '',
Id: null,
deleteId: null,
loading: false,
totalRecords: 0,
isLoading: false,
registros: '',
init() {
this.loadRegister();
},
searchQuery: '',
loadRegister(page = 1) {
this.isLoading = true;
const search = this.searchQuery ? `&search=${this.searchQuery}` : '';
fetch(`/loaddeberesusuario?page=${page}${search}`)
.then(response => response.json())
.then(data => {
console.log(data);
this.totalRecords = data.total;
this.registros = data.records;
this.totalPages = data.totalPages;
this.createPagination(this.totalPages, page);
this.sinregistro = data.records.length === 0;
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.isLoading = false;
});
},
createPagination(totalPages, currentPage) {
const paginationContainer = document.getElementById('pagination');
paginationContainer.innerHTML = '';
const paginationButtonsContainer = document.createElement('div');
paginationButtonsContainer.className = "md:flex mx-auto mb-2 md:justify-between items-center w-full";
const createButton = (text, isDisabled, onClick, additionalClasses = '') => {
const button = document.createElement('button');
button.textContent = text;
button.disabled = isDisabled;
button.className = `px-4 py-2 text-black bg-gray-200 ${additionalClasses} ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-300'}`;
if (!isDisabled) {
button.onclick = onClick;
}
return button;
};
const leftContainer = document.createElement('div');
leftContainer.className = "md:flex text-center mb-2";
leftContainer.appendChild(createButton('Anterior', currentPage === 1, () => this.loadRegister(currentPage - 1), 'rounded-l-md'));
for (let i = 1; i <= totalPages; i++) {
const pageButton = createButton(i, false, () => this.loadRegister(i), `mx-1 ${i === currentPage ? 'bg-black text-gray-50' : 'bg-white text-black'} rounded-md`);
leftContainer.appendChild(pageButton);
}
leftContainer.appendChild(createButton('Siguiente', currentPage === totalPages, () => this.loadRegister(currentPage + 1), 'rounded-r-md'));
paginationButtonsContainer.appendChild(leftContainer);
const recordCountDiv = document.createElement('div');
const startRecord = (currentPage - 1) * 10 + 1;
const endRecord = Math.min(currentPage * 10, this.totalRecords);
recordCountDiv.className = "text-sm text-gray-700 text-center md:ml-auto";
recordCountDiv.innerHTML = `Mostrando ${startRecord} a ${endRecord} de ${this.totalRecords} registros`;
paginationButtonsContainer.appendChild(recordCountDiv);
paginationContainer.appendChild(paginationButtonsContainer);
},
closeModal() {
this.addModal = false;
this.editModal = false;
this.viewModal = false;
this.showConfirmModal = false;
this.newImagen = null; // Reiniciar imagen
this.newArchivo = null; // Reiniciar archivo
this.updateImagen = null; // Reiniciar imagen de actualización
this.updateArchivo = null; // Reiniciar archivo de actualización
},
handleFileChange(event, field) {
if (field === 'imagen') {
this.newImagen = event.target.files[0];
} else if (field === 'archivo') {
this.newArchivo = event.target.files[0];
}
},
handleUpdateFileChange(event, field) {
if (field === 'imagen') {
this.updateImagen = event.target.files[0];
} else if (field === 'archivo') {
this.updateArchivo = event.target.files[0];
}
},
submitAdd() {
this.loading = true;
const formData = new FormData();
formData.append('title', this.newTitle);
formData.append('description', this.newDescription);
formData.append('text_button', this.newText_button); // Guardar el texto
if (this.newImagen) formData.append('imagen', this.newImagen); // Adjuntar la imagen si existe
if (this.newArchivo) formData.append('archivo', this.newArchivo); // Adjuntar el archivo si existe
fetch('/app/deberesusuario', { // Cambié la ruta a 'politicashseq'
method: 'POST',
body: formData
})
.then(response => response.json())
.then(() => {
this.newTitle = '';
this.newDescription = '';
this.newText_button = '';
this.newImagen = null;
this.newArchivo = null;
// Limpia los campos de archivo en el DOM
document.getElementById('imagen').value = ''; // Restablece el input de imagen
document.getElementById('archivo').value = ''; // Restablece el input de archivo
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
submitEdit() {
this.loading = true;
const formData = new FormData();
formData.append('title', this.Title);
formData.append('description', this.Description);
formData.append('text_button', this.Text_button); // Guardar el texto
// Adjuntar la imagen si existe, o usar la ruta de imagen
if (this.updateImagen) {
formData.append('imagen', this.updateImagen);
} else if (this.Imagen) {
formData.append('imagen', this.Imagen);
}
// Adjuntar el archivo si existe, o usar la ruta del archivo
if (this.updateArchivo) {
formData.append('archivo', this.updateArchivo);
} else if (this.Archivo) {
formData.append('archivo', this.Archivo);
}
fetch(`/app/deberesusuario/${this.Id}`, { // Cambié la ruta a 'politicashseq'
method: 'PUT',
body: formData
})
.then(response => response.json())
.then(() => {
this.Title = '';
this.Description = '';
this.Text_button = '';
this.updateImagen = null;
this.updateArchivo = null;
// Limpia los campos de archivo en el DOM
document.getElementById('editImagen').value = ''; // Restablece el input de imagen
document.getElementById('editArchivo').value = ''; // Restablece el input de archivo
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
deleteItem() {
this.loading = true;
fetch(`/app/deberesusuario/${this.deleteId}`, { // Cambié la ruta a 'politicashseq'
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
.then(() => {
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
}
}));
});
</script>
</div>
+19
View File
@@ -0,0 +1,19 @@
<div class="email-container">
<div class="email-header">Hola! 👋 ¡Gracias por registrarte en nuestra oficina virtual! 🖥️</div>
<div class="email-content">
Estamos emocionados de que te unas a nosotros. Antes de empezar, necesitamos confirmar tu dirección de correo electrónico para asegurarnos de que todo esté en orden.<br><br>
🔒 Haz clic en el siguiente botón para confirmar tu registro:
</div>
<a href="{{.confirm_link}}" class="email-button">Confirmar Registro</a>
<div class="email-content">
<p>Si no puedes hacer clic en el botón, copia y pega este enlace en tu navegador:</p>
<a href="{{.confirm_link}}">{{.confirm_link}}</a>
</div>
<div class="footer-note">
📌 ¿No realizaste este registro? Ignora este correo, y tu cuenta no será activada.<br><br>
Bienvenido a nuestra oficina virtual,<br>
El equipo de Gases del Oriente 🚀
</div>
</div>
@@ -0,0 +1,15 @@
<div class="email-container">
<div class="email-header">Hola! 👋 ¡Restablece tu contraseña! 🖥️</div>
<div class="email-content">
Por favor haz click en el siguiente boton para restablecer tu contraseña en la oficina virtual:
</div>
<a href="{{.reset_link}}" class="email-button">Restablecer contraseña</a>
<div class="email-content">
<p>Si no puedes hacer clic en el botón, copia y pega este enlace en tu navegador:</p>
<a href="{{.reset_link}}">{{.reset_link}}</a>
</div>
<div class="footer-note">
📌 ¿No realizaste esta solicitud? Ignora este correo..<br>
El equipo de Gases del Oriente 🚀
</div>
</div>
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title>We've got some trouble | 400 - Bad Request</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */body,html{width:100%;height:100%;background-color:#21232a}body{color:#fff;text-align:center;text-shadow:0 2px 4px rgba(0,0,0,.5);padding:0;min-height:100%;-webkit-box-shadow:inset 0 0 100px rgba(0,0,0,.8);box-shadow:inset 0 0 100px rgba(0,0,0,.8);display:table;font-family:"Open Sans",Arial,sans-serif}h1{font-family:inherit;font-weight:500;line-height:1.1;color:inherit;font-size:36px}h1 small{font-size:68%;font-weight:400;line-height:1;color:#777}a{text-decoration:none;color:#fff;font-size:inherit;border-bottom:dotted 1px #707070}.lead{color:silver;font-size:21px;line-height:1.4}.cover{display:table-cell;vertical-align:middle;padding:0 20px}footer{position:fixed;width:100%;height:40px;left:0;bottom:0;color:#a0a0a0;font-size:14px}</style>
</head>
<body>
<div class="cover"><h1>Bad Request <small>Error 400</small></h1><p class="lead">The server cannot process the request due to something that is perceived to be a client error.</p></div>
</body>
</html>
+285
View File
@@ -0,0 +1,285 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>We've got some trouble | 401 - Unauthorized</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
article, aside, footer, header, nav, section {
display: block
}
h1 {
font-size: 2em;
margin: .67em 0
}
figcaption, figure, main {
display: block
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible
}
pre {
font-family: monospace, monospace;
font-size: 1em
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active, a:hover {
outline-width: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted
}
b, strong {
font-weight: inherit
}
b, strong {
font-weight: bolder
}
code, kbd, samp {
font-family: monospace, monospace;
font-size: 1em
}
dfn {
font-style: italic
}
mark {
background-color: #ff0;
color: #000
}
small {
font-size: 80%
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
audio, video {
display: inline-block
}
audio:not([controls]) {
display: none;
height: 0
}
img {
border-style: none
}
svg:not(:root) {
overflow: hidden
}
button, input, optgroup, select, textarea {
font-family: sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0
}
button, input {
overflow: visible
}
button, select {
text-transform: none
}
[type=reset], [type=submit], button, html [type=button] {
-webkit-appearance: button
}
[type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner {
border-style: none;
padding: 0
}
[type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring, button:-moz-focusring {
outline: 1px dotted ButtonText
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal
}
progress {
display: inline-block;
vertical-align: baseline
}
textarea {
overflow: auto
}
[type=checkbox], [type=radio] {
box-sizing: border-box;
padding: 0
}
[type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
[type=search]::-webkit-search-cancel-button, [type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
details, menu {
display: block
}
summary {
display: list-item
}
canvas {
display: inline-block
}
template {
display: none
}
[hidden] {
display: none
}
/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */
body, html {
width: 100%;
height: 100%;
background-color: #21232a
}
body {
color: #fff;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, .5);
padding: 0;
min-height: 100%;
-webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
display: table;
font-family: "Open Sans", Arial, sans-serif
}
h1 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
font-size: 36px
}
h1 small {
font-size: 68%;
font-weight: 400;
line-height: 1;
color: #777
}
a {
text-decoration: none;
color: #fff;
font-size: inherit;
border-bottom: dotted 1px #707070
}
.lead {
color: silver;
font-size: 21px;
line-height: 1.4
}
.cover {
display: table-cell;
vertical-align: middle;
padding: 0 20px
}
footer {
position: fixed;
width: 100%;
height: 40px;
left: 0;
bottom: 0;
color: #a0a0a0;
font-size: 14px
}</style>
</head>
<body>
<div class="cover"><h1>Unauthorized <small>Error 401</small></h1>
<p class="lead">The requested resource requires an authentication.</p></div>
</body>
</html>
+285
View File
@@ -0,0 +1,285 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>We've got some trouble | 403 - Access Denied</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
article, aside, footer, header, nav, section {
display: block
}
h1 {
font-size: 2em;
margin: .67em 0
}
figcaption, figure, main {
display: block
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible
}
pre {
font-family: monospace, monospace;
font-size: 1em
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active, a:hover {
outline-width: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted
}
b, strong {
font-weight: inherit
}
b, strong {
font-weight: bolder
}
code, kbd, samp {
font-family: monospace, monospace;
font-size: 1em
}
dfn {
font-style: italic
}
mark {
background-color: #ff0;
color: #000
}
small {
font-size: 80%
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
audio, video {
display: inline-block
}
audio:not([controls]) {
display: none;
height: 0
}
img {
border-style: none
}
svg:not(:root) {
overflow: hidden
}
button, input, optgroup, select, textarea {
font-family: sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0
}
button, input {
overflow: visible
}
button, select {
text-transform: none
}
[type=reset], [type=submit], button, html [type=button] {
-webkit-appearance: button
}
[type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner {
border-style: none;
padding: 0
}
[type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring, button:-moz-focusring {
outline: 1px dotted ButtonText
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal
}
progress {
display: inline-block;
vertical-align: baseline
}
textarea {
overflow: auto
}
[type=checkbox], [type=radio] {
box-sizing: border-box;
padding: 0
}
[type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
[type=search]::-webkit-search-cancel-button, [type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
details, menu {
display: block
}
summary {
display: list-item
}
canvas {
display: inline-block
}
template {
display: none
}
[hidden] {
display: none
}
/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */
body, html {
width: 100%;
height: 100%;
background-color: #21232a
}
body {
color: #fff;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, .5);
padding: 0;
min-height: 100%;
-webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
display: table;
font-family: "Open Sans", Arial, sans-serif
}
h1 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
font-size: 36px
}
h1 small {
font-size: 68%;
font-weight: 400;
line-height: 1;
color: #777
}
a {
text-decoration: none;
color: #fff;
font-size: inherit;
border-bottom: dotted 1px #707070
}
.lead {
color: silver;
font-size: 21px;
line-height: 1.4
}
.cover {
display: table-cell;
vertical-align: middle;
padding: 0 20px
}
footer {
position: fixed;
width: 100%;
height: 40px;
left: 0;
bottom: 0;
color: #a0a0a0;
font-size: 14px
}</style>
</head>
<body>
<div class="cover"><h1>Access Denied <small>Error 403</small></h1>
<p class="lead">The requested resource requires an authentication.</p></div>
</body>
</html>
+285
View File
@@ -0,0 +1,285 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>We've got some trouble | 404 - Resource not found</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
article, aside, footer, header, nav, section {
display: block
}
h1 {
font-size: 2em;
margin: .67em 0
}
figcaption, figure, main {
display: block
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible
}
pre {
font-family: monospace, monospace;
font-size: 1em
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active, a:hover {
outline-width: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted
}
b, strong {
font-weight: inherit
}
b, strong {
font-weight: bolder
}
code, kbd, samp {
font-family: monospace, monospace;
font-size: 1em
}
dfn {
font-style: italic
}
mark {
background-color: #ff0;
color: #000
}
small {
font-size: 80%
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
audio, video {
display: inline-block
}
audio:not([controls]) {
display: none;
height: 0
}
img {
border-style: none
}
svg:not(:root) {
overflow: hidden
}
button, input, optgroup, select, textarea {
font-family: sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0
}
button, input {
overflow: visible
}
button, select {
text-transform: none
}
[type=reset], [type=submit], button, html [type=button] {
-webkit-appearance: button
}
[type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner {
border-style: none;
padding: 0
}
[type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring, button:-moz-focusring {
outline: 1px dotted ButtonText
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal
}
progress {
display: inline-block;
vertical-align: baseline
}
textarea {
overflow: auto
}
[type=checkbox], [type=radio] {
box-sizing: border-box;
padding: 0
}
[type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
[type=search]::-webkit-search-cancel-button, [type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
details, menu {
display: block
}
summary {
display: list-item
}
canvas {
display: inline-block
}
template {
display: none
}
[hidden] {
display: none
}
/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */
body, html {
width: 100%;
height: 100%;
background-color: #21232a
}
body {
color: #fff;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, .5);
padding: 0;
min-height: 100%;
-webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
display: table;
font-family: "Open Sans", Arial, sans-serif
}
h1 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
font-size: 36px
}
h1 small {
font-size: 68%;
font-weight: 400;
line-height: 1;
color: #777
}
a {
text-decoration: none;
color: #fff;
font-size: inherit;
border-bottom: dotted 1px #707070
}
.lead {
color: silver;
font-size: 21px;
line-height: 1.4
}
.cover {
display: table-cell;
vertical-align: middle;
padding: 0 20px
}
footer {
position: fixed;
width: 100%;
height: 40px;
left: 0;
bottom: 0;
color: #a0a0a0;
font-size: 14px
}</style>
</head>
<body>
<div class="cover"><h1>Resource not found <small>Error 404</small></h1>
<p class="lead">The requested resource could not be found but may be available again in the future.</p></div>
<footer><p>Technical Contact: <a href="mailto:x@example.com">x@example.com</a></p></footer>
</body>
</html>
+293
View File
@@ -0,0 +1,293 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>We've got some trouble | 500 - Webservice currently unavailable</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
article, aside, footer, header, nav, section {
display: block
}
h1 {
font-size: 2em;
margin: .67em 0
}
figcaption, figure, main {
display: block
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible
}
pre {
font-family: monospace, monospace;
font-size: 1em
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active, a:hover {
outline-width: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted
}
b, strong {
font-weight: inherit
}
b, strong {
font-weight: bolder
}
code, kbd, samp {
font-family: monospace, monospace;
font-size: 1em
}
dfn {
font-style: italic
}
mark {
background-color: #ff0;
color: #000
}
small {
font-size: 80%
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
audio, video {
display: inline-block
}
audio:not([controls]) {
display: none;
height: 0
}
img {
border-style: none
}
svg:not(:root) {
overflow: hidden
}
button, input, optgroup, select, textarea {
font-family: sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0
}
button, input {
overflow: visible
}
button, select {
text-transform: none
}
[type=reset], [type=submit], button, html [type=button] {
-webkit-appearance: button
}
[type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner {
border-style: none;
padding: 0
}
[type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring, button:-moz-focusring {
outline: 1px dotted ButtonText
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal
}
progress {
display: inline-block;
vertical-align: baseline
}
textarea {
overflow: auto
}
[type=checkbox], [type=radio] {
box-sizing: border-box;
padding: 0
}
[type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
[type=search]::-webkit-search-cancel-button, [type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
details, menu {
display: block
}
summary {
display: list-item
}
canvas {
display: inline-block
}
template {
display: none
}
[hidden] {
display: none
}
/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */
body, html {
width: 100%;
height: 100%;
background-color: #21232a
}
body {
color: #fff;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, .5);
padding: 0;
min-height: 100%;
-webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
display: table;
font-family: "Open Sans", Arial, sans-serif
}
h1 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
font-size: 36px
}
h1 small {
font-size: 68%;
font-weight: 400;
line-height: 1;
color: #777
}
a {
text-decoration: none;
color: #fff;
font-size: inherit;
border-bottom: dotted 1px #707070
}
.lead {
color: silver;
font-size: 21px;
line-height: 1.4
}
.cover {
display: table-cell;
vertical-align: middle;
padding: 0 20px
}
footer {
position: fixed;
width: 100%;
height: 40px;
left: 0;
bottom: 0;
color: #a0a0a0;
font-size: 14px
}</style>
</head>
<body>
<div class="cover"><h1>Webservice currently unavailable <small>Error 500</small></h1>
<p class="lead">An unexpected condition was encountered.<br/>Our service team has been dispatched to bring it back
online.
</p>
<p class="lead">
<code>
{{ .error }}
</code>
</p>
</div>
<footer><p>Technical Contact: <a href="mailto:x@example.com">x@example.com</a></p></footer>
</body>
</html>
+285
View File
@@ -0,0 +1,285 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>We've got some trouble | 501 - Not Implemented</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
article, aside, footer, header, nav, section {
display: block
}
h1 {
font-size: 2em;
margin: .67em 0
}
figcaption, figure, main {
display: block
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible
}
pre {
font-family: monospace, monospace;
font-size: 1em
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active, a:hover {
outline-width: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted
}
b, strong {
font-weight: inherit
}
b, strong {
font-weight: bolder
}
code, kbd, samp {
font-family: monospace, monospace;
font-size: 1em
}
dfn {
font-style: italic
}
mark {
background-color: #ff0;
color: #000
}
small {
font-size: 80%
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
audio, video {
display: inline-block
}
audio:not([controls]) {
display: none;
height: 0
}
img {
border-style: none
}
svg:not(:root) {
overflow: hidden
}
button, input, optgroup, select, textarea {
font-family: sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0
}
button, input {
overflow: visible
}
button, select {
text-transform: none
}
[type=reset], [type=submit], button, html [type=button] {
-webkit-appearance: button
}
[type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner {
border-style: none;
padding: 0
}
[type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring, button:-moz-focusring {
outline: 1px dotted ButtonText
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal
}
progress {
display: inline-block;
vertical-align: baseline
}
textarea {
overflow: auto
}
[type=checkbox], [type=radio] {
box-sizing: border-box;
padding: 0
}
[type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
[type=search]::-webkit-search-cancel-button, [type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
details, menu {
display: block
}
summary {
display: list-item
}
canvas {
display: inline-block
}
template {
display: none
}
[hidden] {
display: none
}
/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */
body, html {
width: 100%;
height: 100%;
background-color: #21232a
}
body {
color: #fff;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, .5);
padding: 0;
min-height: 100%;
-webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
display: table;
font-family: "Open Sans", Arial, sans-serif
}
h1 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
font-size: 36px
}
h1 small {
font-size: 68%;
font-weight: 400;
line-height: 1;
color: #777
}
a {
text-decoration: none;
color: #fff;
font-size: inherit;
border-bottom: dotted 1px #707070
}
.lead {
color: silver;
font-size: 21px;
line-height: 1.4
}
.cover {
display: table-cell;
vertical-align: middle;
padding: 0 20px
}
footer {
position: fixed;
width: 100%;
height: 40px;
left: 0;
bottom: 0;
color: #a0a0a0;
font-size: 14px
}</style>
</head>
<body>
<div class="cover"><h1>Not Implemented <small>Error 501</small></h1>
<p class="lead">The Webserver cannot recognize the request method.</p></div>
</body>
</html>
+286
View File
@@ -0,0 +1,286 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>We've got some trouble | 502 - Webservice currently unavailable</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
article, aside, footer, header, nav, section {
display: block
}
h1 {
font-size: 2em;
margin: .67em 0
}
figcaption, figure, main {
display: block
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible
}
pre {
font-family: monospace, monospace;
font-size: 1em
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active, a:hover {
outline-width: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted
}
b, strong {
font-weight: inherit
}
b, strong {
font-weight: bolder
}
code, kbd, samp {
font-family: monospace, monospace;
font-size: 1em
}
dfn {
font-style: italic
}
mark {
background-color: #ff0;
color: #000
}
small {
font-size: 80%
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
audio, video {
display: inline-block
}
audio:not([controls]) {
display: none;
height: 0
}
img {
border-style: none
}
svg:not(:root) {
overflow: hidden
}
button, input, optgroup, select, textarea {
font-family: sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0
}
button, input {
overflow: visible
}
button, select {
text-transform: none
}
[type=reset], [type=submit], button, html [type=button] {
-webkit-appearance: button
}
[type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner {
border-style: none;
padding: 0
}
[type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring, button:-moz-focusring {
outline: 1px dotted ButtonText
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal
}
progress {
display: inline-block;
vertical-align: baseline
}
textarea {
overflow: auto
}
[type=checkbox], [type=radio] {
box-sizing: border-box;
padding: 0
}
[type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
[type=search]::-webkit-search-cancel-button, [type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
details, menu {
display: block
}
summary {
display: list-item
}
canvas {
display: inline-block
}
template {
display: none
}
[hidden] {
display: none
}
/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */
body, html {
width: 100%;
height: 100%;
background-color: #21232a
}
body {
color: #fff;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, .5);
padding: 0;
min-height: 100%;
-webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
display: table;
font-family: "Open Sans", Arial, sans-serif
}
h1 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
font-size: 36px
}
h1 small {
font-size: 68%;
font-weight: 400;
line-height: 1;
color: #777
}
a {
text-decoration: none;
color: #fff;
font-size: inherit;
border-bottom: dotted 1px #707070
}
.lead {
color: silver;
font-size: 21px;
line-height: 1.4
}
.cover {
display: table-cell;
vertical-align: middle;
padding: 0 20px
}
footer {
position: fixed;
width: 100%;
height: 40px;
left: 0;
bottom: 0;
color: #a0a0a0;
font-size: 14px
}</style>
</head>
<body>
<div class="cover"><h1>Webservice currently unavailable <small>Error 502</small></h1>
<p class="lead">We&#39;ve got some trouble with our backend upstream cluster.<br/>Our service team has been
dispatched to bring it back online.</p></div>
</body>
</html>
+286
View File
@@ -0,0 +1,286 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>We've got some trouble | 503 - Webservice currently unavailable</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
article, aside, footer, header, nav, section {
display: block
}
h1 {
font-size: 2em;
margin: .67em 0
}
figcaption, figure, main {
display: block
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible
}
pre {
font-family: monospace, monospace;
font-size: 1em
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active, a:hover {
outline-width: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted
}
b, strong {
font-weight: inherit
}
b, strong {
font-weight: bolder
}
code, kbd, samp {
font-family: monospace, monospace;
font-size: 1em
}
dfn {
font-style: italic
}
mark {
background-color: #ff0;
color: #000
}
small {
font-size: 80%
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
audio, video {
display: inline-block
}
audio:not([controls]) {
display: none;
height: 0
}
img {
border-style: none
}
svg:not(:root) {
overflow: hidden
}
button, input, optgroup, select, textarea {
font-family: sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0
}
button, input {
overflow: visible
}
button, select {
text-transform: none
}
[type=reset], [type=submit], button, html [type=button] {
-webkit-appearance: button
}
[type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner {
border-style: none;
padding: 0
}
[type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring, button:-moz-focusring {
outline: 1px dotted ButtonText
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal
}
progress {
display: inline-block;
vertical-align: baseline
}
textarea {
overflow: auto
}
[type=checkbox], [type=radio] {
box-sizing: border-box;
padding: 0
}
[type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
[type=search]::-webkit-search-cancel-button, [type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
details, menu {
display: block
}
summary {
display: list-item
}
canvas {
display: inline-block
}
template {
display: none
}
[hidden] {
display: none
}
/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */
body, html {
width: 100%;
height: 100%;
background-color: #21232a
}
body {
color: #fff;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, .5);
padding: 0;
min-height: 100%;
-webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
display: table;
font-family: "Open Sans", Arial, sans-serif
}
h1 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
font-size: 36px
}
h1 small {
font-size: 68%;
font-weight: 400;
line-height: 1;
color: #777
}
a {
text-decoration: none;
color: #fff;
font-size: inherit;
border-bottom: dotted 1px #707070
}
.lead {
color: silver;
font-size: 21px;
line-height: 1.4
}
.cover {
display: table-cell;
vertical-align: middle;
padding: 0 20px
}
footer {
position: fixed;
width: 100%;
height: 40px;
left: 0;
bottom: 0;
color: #a0a0a0;
font-size: 14px
}</style>
</head>
<body>
<div class="cover"><h1>Webservice currently unavailable <small>Error 503</small></h1>
<p class="lead">We&#39;ve got some trouble with our backend upstream cluster.<br/>Our service team has been
dispatched to bring it back online.</p></div>
</body>
</html>
+285
View File
@@ -0,0 +1,285 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>We've got some trouble | 520 - Origin Error - Unknown Host</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
article, aside, footer, header, nav, section {
display: block
}
h1 {
font-size: 2em;
margin: .67em 0
}
figcaption, figure, main {
display: block
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible
}
pre {
font-family: monospace, monospace;
font-size: 1em
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active, a:hover {
outline-width: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted
}
b, strong {
font-weight: inherit
}
b, strong {
font-weight: bolder
}
code, kbd, samp {
font-family: monospace, monospace;
font-size: 1em
}
dfn {
font-style: italic
}
mark {
background-color: #ff0;
color: #000
}
small {
font-size: 80%
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
audio, video {
display: inline-block
}
audio:not([controls]) {
display: none;
height: 0
}
img {
border-style: none
}
svg:not(:root) {
overflow: hidden
}
button, input, optgroup, select, textarea {
font-family: sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0
}
button, input {
overflow: visible
}
button, select {
text-transform: none
}
[type=reset], [type=submit], button, html [type=button] {
-webkit-appearance: button
}
[type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner {
border-style: none;
padding: 0
}
[type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring, button:-moz-focusring {
outline: 1px dotted ButtonText
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal
}
progress {
display: inline-block;
vertical-align: baseline
}
textarea {
overflow: auto
}
[type=checkbox], [type=radio] {
box-sizing: border-box;
padding: 0
}
[type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
[type=search]::-webkit-search-cancel-button, [type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
details, menu {
display: block
}
summary {
display: list-item
}
canvas {
display: inline-block
}
template {
display: none
}
[hidden] {
display: none
}
/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */
body, html {
width: 100%;
height: 100%;
background-color: #21232a
}
body {
color: #fff;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, .5);
padding: 0;
min-height: 100%;
-webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
display: table;
font-family: "Open Sans", Arial, sans-serif
}
h1 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
font-size: 36px
}
h1 small {
font-size: 68%;
font-weight: 400;
line-height: 1;
color: #777
}
a {
text-decoration: none;
color: #fff;
font-size: inherit;
border-bottom: dotted 1px #707070
}
.lead {
color: silver;
font-size: 21px;
line-height: 1.4
}
.cover {
display: table-cell;
vertical-align: middle;
padding: 0 20px
}
footer {
position: fixed;
width: 100%;
height: 40px;
left: 0;
bottom: 0;
color: #a0a0a0;
font-size: 14px
}</style>
</head>
<body>
<div class="cover"><h1>Origin Error - Unknown Host <small>Error 520</small></h1>
<p class="lead">The requested hostname is not routed. Use only hostnames to access resources.</p></div>
</body>
</html>
+286
View File
@@ -0,0 +1,286 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>We've got some trouble | 521 - Webservice currently unavailable</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
article, aside, footer, header, nav, section {
display: block
}
h1 {
font-size: 2em;
margin: .67em 0
}
figcaption, figure, main {
display: block
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible
}
pre {
font-family: monospace, monospace;
font-size: 1em
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active, a:hover {
outline-width: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted
}
b, strong {
font-weight: inherit
}
b, strong {
font-weight: bolder
}
code, kbd, samp {
font-family: monospace, monospace;
font-size: 1em
}
dfn {
font-style: italic
}
mark {
background-color: #ff0;
color: #000
}
small {
font-size: 80%
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
audio, video {
display: inline-block
}
audio:not([controls]) {
display: none;
height: 0
}
img {
border-style: none
}
svg:not(:root) {
overflow: hidden
}
button, input, optgroup, select, textarea {
font-family: sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0
}
button, input {
overflow: visible
}
button, select {
text-transform: none
}
[type=reset], [type=submit], button, html [type=button] {
-webkit-appearance: button
}
[type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner {
border-style: none;
padding: 0
}
[type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring, button:-moz-focusring {
outline: 1px dotted ButtonText
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal
}
progress {
display: inline-block;
vertical-align: baseline
}
textarea {
overflow: auto
}
[type=checkbox], [type=radio] {
box-sizing: border-box;
padding: 0
}
[type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
[type=search]::-webkit-search-cancel-button, [type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
details, menu {
display: block
}
summary {
display: list-item
}
canvas {
display: inline-block
}
template {
display: none
}
[hidden] {
display: none
}
/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */
body, html {
width: 100%;
height: 100%;
background-color: #21232a
}
body {
color: #fff;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, .5);
padding: 0;
min-height: 100%;
-webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
display: table;
font-family: "Open Sans", Arial, sans-serif
}
h1 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
font-size: 36px
}
h1 small {
font-size: 68%;
font-weight: 400;
line-height: 1;
color: #777
}
a {
text-decoration: none;
color: #fff;
font-size: inherit;
border-bottom: dotted 1px #707070
}
.lead {
color: silver;
font-size: 21px;
line-height: 1.4
}
.cover {
display: table-cell;
vertical-align: middle;
padding: 0 20px
}
footer {
position: fixed;
width: 100%;
height: 40px;
left: 0;
bottom: 0;
color: #a0a0a0;
font-size: 14px
}</style>
</head>
<body>
<div class="cover"><h1>Webservice currently unavailable <small>Error 521</small></h1>
<p class="lead">We&#39;ve got some trouble with our backend upstream cluster.<br/>Our service team has been
dispatched to bring it back online.</p></div>
</body>
</html>
+286
View File
@@ -0,0 +1,286 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Simple HttpErrorPages | MIT License | https://github.com/AndiDittrich/HttpErrorPages -->
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>We've got some trouble | 533 - Scheduled Maintenance</title>
<style type="text/css">/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
article, aside, footer, header, nav, section {
display: block
}
h1 {
font-size: 2em;
margin: .67em 0
}
figcaption, figure, main {
display: block
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible
}
pre {
font-family: monospace, monospace;
font-size: 1em
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active, a:hover {
outline-width: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted
}
b, strong {
font-weight: inherit
}
b, strong {
font-weight: bolder
}
code, kbd, samp {
font-family: monospace, monospace;
font-size: 1em
}
dfn {
font-style: italic
}
mark {
background-color: #ff0;
color: #000
}
small {
font-size: 80%
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
audio, video {
display: inline-block
}
audio:not([controls]) {
display: none;
height: 0
}
img {
border-style: none
}
svg:not(:root) {
overflow: hidden
}
button, input, optgroup, select, textarea {
font-family: sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0
}
button, input {
overflow: visible
}
button, select {
text-transform: none
}
[type=reset], [type=submit], button, html [type=button] {
-webkit-appearance: button
}
[type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner, button::-moz-focus-inner {
border-style: none;
padding: 0
}
[type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring, button:-moz-focusring {
outline: 1px dotted ButtonText
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal
}
progress {
display: inline-block;
vertical-align: baseline
}
textarea {
overflow: auto
}
[type=checkbox], [type=radio] {
box-sizing: border-box;
padding: 0
}
[type=number]::-webkit-inner-spin-button, [type=number]::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
[type=search]::-webkit-search-cancel-button, [type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
details, menu {
display: block
}
summary {
display: list-item
}
canvas {
display: inline-block
}
template {
display: none
}
[hidden] {
display: none
}
/*! Simple HttpErrorPages | MIT X11 License | https://github.com/AndiDittrich/HttpErrorPages */
body, html {
width: 100%;
height: 100%;
background-color: #21232a
}
body {
color: #fff;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, .5);
padding: 0;
min-height: 100%;
-webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
box-shadow: inset 0 0 100px rgba(0, 0, 0, .8);
display: table;
font-family: "Open Sans", Arial, sans-serif
}
h1 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
font-size: 36px
}
h1 small {
font-size: 68%;
font-weight: 400;
line-height: 1;
color: #777
}
a {
text-decoration: none;
color: #fff;
font-size: inherit;
border-bottom: dotted 1px #707070
}
.lead {
color: silver;
font-size: 21px;
line-height: 1.4
}
.cover {
display: table-cell;
vertical-align: middle;
padding: 0 20px
}
footer {
position: fixed;
width: 100%;
height: 40px;
left: 0;
bottom: 0;
color: #a0a0a0;
font-size: 14px
}</style>
</head>
<body>
<div class="cover"><h1>Scheduled Maintenance <small>Error 533</small></h1>
<p class="lead">This site is currently down for maintenance.<br/>Our service team is working hard to bring it back
online soon.</p></div>
</body>
</html>
+4
View File
@@ -0,0 +1,4 @@
{{template "partials/file-upload" .}}
{{template "partials/footer" .}}
+223
View File
@@ -0,0 +1,223 @@
<div class="float-left flex-row w-3/4 scrolling-auto">
<div>
<div>
<h2 class="text-2xl font-semibold leading-tight">Users</h2>
</div>
<div class="my-2 flex sm:flex-row flex-col">
<div class="flex flex-row mb-1 sm:mb-0">
<div class="relative">
<select
class="appearance-none h-full rounded-l border block appearance-none w-full bg-white border-gray-400 text-gray-700 py-2 px-4 pr-8 leading-tight focus:outline-none focus:bg-white focus:border-gray-500">
<option>5</option>
<option>10</option>
<option>20</option>
</select>
<div
class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
</svg>
</div>
</div>
<div class="relative">
<select
class="appearance-none h-full rounded-r border-t sm:rounded-r-none sm:border-r-0 border-r border-b block appearance-none w-full bg-white border-gray-400 text-gray-700 py-2 px-4 pr-8 leading-tight focus:outline-none focus:border-l focus:border-r focus:bg-white focus:border-gray-500">
<option>All</option>
<option>Active</option>
<option>Inactive</option>
</select>
<div
class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
</svg>
</div>
</div>
</div>
<div class="block relative">
<span class="h-full absolute inset-y-0 left-0 flex items-center pl-2">
<svg viewBox="0 0 24 24" class="h-4 w-4 fill-current text-gray-500">
<path
d="M10 4a6 6 0 100 12 6 6 0 000-12zm-8 6a8 8 0 1114.32 4.906l5.387 5.387a1 1 0 01-1.414 1.414l-5.387-5.387A8 8 0 012 10z">
</path>
</svg>
</span>
<input placeholder="Search"
class="appearance-none rounded-r rounded-l sm:rounded-l-none border border-gray-400 border-b block pl-8 pr-6 py-2 w-full bg-white text-sm placeholder-gray-400 text-gray-700 focus:bg-white focus:placeholder-gray-600 focus:text-gray-700 focus:outline-none"/>
</div>
</div>
<div class="-mx-4 sm:-mx-8 px-4 sm:px-8 py-4 overflow-x-auto">
<div class="inline-block min-w-full shadow rounded-lg overflow-hidden">
<table class="min-w-full leading-normal">
<thead>
<tr>
<th
class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
User
</th>
<th
class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
Rol
</th>
<th
class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
Created at
</th>
<th
class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
Status
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<div class="flex items-center">
<div class="flex-shrink-0 w-10 h-10">
<img class="w-full h-full rounded-full"
src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.2&w=160&h=160&q=80"
alt=""/>
</div>
<div class="ml-3">
<p class="text-gray-900 whitespace-no-wrap">
Vera Carpenter
</p>
</div>
</div>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<p class="text-gray-900 whitespace-no-wrap">Admin</p>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<p class="text-gray-900 whitespace-no-wrap">
Jan 21, 2020
</p>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<span
class="relative inline-block px-3 py-1 font-semibold text-green-900 leading-tight">
<span aria-hidden
class="absolute inset-0 bg-green-200 opacity-50 rounded-full"></span>
<span class="relative">Activo</span>
</span>
</td>
</tr>
<tr>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<div class="flex items-center">
<div class="flex-shrink-0 w-10 h-10">
<img class="w-full h-full rounded-full"
src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.2&w=160&h=160&q=80"
alt=""/>
</div>
<div class="ml-3">
<p class="text-gray-900 whitespace-no-wrap">
Blake Bowman
</p>
</div>
</div>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<p class="text-gray-900 whitespace-no-wrap">Editor</p>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<p class="text-gray-900 whitespace-no-wrap">
Jan 01, 2020
</p>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<span
class="relative inline-block px-3 py-1 font-semibold text-green-900 leading-tight">
<span aria-hidden
class="absolute inset-0 bg-green-200 opacity-50 rounded-full"></span>
<span class="relative">Activo</span>
</span>
</td>
</tr>
<tr>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<div class="flex items-center">
<div class="flex-shrink-0 w-10 h-10">
<img class="w-full h-full rounded-full"
src="https://images.unsplash.com/photo-1540845511934-7721dd7adec3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.2&w=160&h=160&q=80"
alt=""/>
</div>
<div class="ml-3">
<p class="text-gray-900 whitespace-no-wrap">
Dana Moore
</p>
</div>
</div>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<p class="text-gray-900 whitespace-no-wrap">Editor</p>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<p class="text-gray-900 whitespace-no-wrap">
Jan 10, 2020
</p>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<span
class="relative inline-block px-3 py-1 font-semibold text-orange-900 leading-tight">
<span aria-hidden
class="absolute inset-0 bg-orange-200 opacity-50 rounded-full"></span>
<span class="relative">Suspended</span>
</span>
</td>
</tr>
<tr>
<td class="px-5 py-5 bg-white text-sm">
<div class="flex items-center">
<div class="flex-shrink-0 w-10 h-10">
<img class="w-full h-full rounded-full"
src="https://images.unsplash.com/photo-1522609925277-66fea332c575?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.2&h=160&w=160&q=80"
alt=""/>
</div>
<div class="ml-3">
<p class="text-gray-900 whitespace-no-wrap">
Alonzo Cox
</p>
</div>
</div>
</td>
<td class="px-5 py-5 bg-white text-sm">
<p class="text-gray-900 whitespace-no-wrap">Admin</p>
</td>
<td class="px-5 py-5 bg-white text-sm">
<p class="text-gray-900 whitespace-no-wrap">Jan 18, 2020</p>
</td>
<td class="px-5 py-5 bg-white text-sm">
<span
class="relative inline-block px-3 py-1 font-semibold text-red-900 leading-tight">
<span aria-hidden
class="absolute inset-0 bg-red-200 opacity-50 rounded-full"></span>
<span class="relative">Inactive</span>
</span>
</td>
</tr>
</tbody>
</table>
<div
class="px-5 py-5 bg-white border-t flex flex-col xs:flex-row items-center xs:justify-between ">
<span class="text-xs xs:text-sm text-gray-900">
Showing 1 to 4 of 50 Entries
</span>
<div class="inline-flex mt-2 xs:mt-0">
<button
class="text-sm bg-gray-300 hover:bg-gray-400 text-gray-800 font-semibold py-2 px-4 rounded-l">
Prev
</button>
<button
class="text-sm bg-gray-300 hover:bg-gray-400 text-gray-800 font-semibold py-2 px-4 rounded-r">
Next
</button>
</div>
</div>
</div>
</div>
</div>
</div>
{{template "partials/file-upload" .}}
{{template "partials/footer" .}}
+76
View File
@@ -0,0 +1,76 @@
<h1>{{.Title}}</h1>
{{if .user}}
Hello, {{.user.FirstName}} - ID: {{.user.ID}}!
{{end}}
<div class="bg-white rounded-lg w-1/4 lg:w-1/4 xl:w-1/4 p-4 shadow float-right flex-col">
<div>
<span class="text-gray-900 relative inline-block date uppercase font-medium tracking-widest">Wednesday 8</span>
<div class="flex mb-2">
<div class="w-2/12">
<span class="text-sm text-gray-600 block">8:00a</span>
<span class="text-sm text-gray-600 block">8:15a</span>
</div>
<div class="w-1/12">
<span class="bg-blue-400 h-2 w-2 rounded-full block mt-2"></span>
</div>
<div class="w-9/12">
<span class="text-sm font-semibold block">Morning Standup</span>
<span class="text-sm">Zoom ID: 1134 11 1134</span>
</div>
</div>
<div class="flex mb-4">
<div class="w-2/12">
<span class="text-sm text-gray-600 block">10:00a</span>
<span class="text-sm text-gray-600 block">2:00p</span>
</div>
<div class="w-1/12">
<span class="bg-red-400 h-2 w-2 rounded-full block mt-2"></span>
</div>
<div class="w-9/12">
<span class="text-sm font-semibold block">Core Development</span>
<span class="text-sm">Joey, Matt, CJ and Vlad</span>
</div>
</div>
<div class="flex mb-4">
<div class="w-2/12">
<span class="text-sm text-gray-600 block">3:00p</span>
<span class="text-sm text-gray-600 block">3:30p</span>
</div>
<div class="w-1/12">
<span class="bg-indigo-600 h-2 w-2 rounded-full block mt-2"></span>
</div>
<div class="w-9/12">
<span class="text-sm font-semibold block">Interview with Ed Harris</span>
</div>
</div>
</div>
<div>
<span class="text-gray-900 relative inline-block date uppercase font-medium tracking-widest">Thursday 9</span>
<div class="flex mb-2">
<div class="w-2/12">
<span class="text-sm text-gray-600 block">8:00a</span>
<span class="text-sm text-gray-600 block">8:15a</span>
</div>
<div class="w-1/12">
<span class="bg-blue-400 h-2 w-2 rounded-full block mt-2"></span>
</div>
<div class="w-9/12">
<span class="text-sm font-semibold block">Morning Standup</span>
<span class="text-sm">Zoom ID: 1134 11 1134</span>
</div>
</div>
<div class="flex mb-4">
<div class="w-2/12">
<span class="text-sm text-gray-600 block">6:00p</span>
<span class="text-sm text-gray-600 block">7:30p</span>
</div>
<div class="w-1/12">
<span class="bg-yellow-400 h-2 w-2 rounded-full block mt-2"></span>
</div>
<div class="w-9/12">
<span class="text-sm font-semibold block">Dinner with Mom</span>
</div>
</div>
</div>
</div>
{{template "partials/footer" .}}
+10
View File
@@ -0,0 +1,10 @@
<h1>{{.Title}}</h1>
{{if .user}}
Hello, {{.user.FirstName}} - ID: {{.user.ID}}!
<script src="/js/payment.js"></script>
{{end}}
<script src="/js/manifest.js"></script>
<script src="/js/vendor.js"></script>
{{template "partials/footer" .}}
+401
View File
@@ -0,0 +1,401 @@
<!-- Container Principal -->
<div x-data="modals" @keydown.escape="closeModal" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full ">
<h1 class="text-2xl font-bold mb-4">Lista de inicio </h1>
<div class="justify-between w-full md:flex">
<p class="mb-4 text-sm">Gestión de inicio.</p>
<!-- Campo de Búsqueda -->
<div class="mb-4 relative">
<input type="text" x-model="searchQuery" placeholder="Buscar..."
class="border border-gray-300 rounded w-full md:w-60 p-2 text-sm" @input="loadRegister()">
<!-- Contenedor para el botón o el ícono de carga -->
<div x-show="isLoading" class="absolute right-2 top-2 flex items-center">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
</svg>
</div>
</div>
<!-- Botón Nuevo registro -->
<div class="flex justify-end mb-4">
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded text-sm"
x-on:click="addModal=true">Nuevo</button>
</div>
</div>
<!-- Agregar el GIF de carga -->
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<div class="overflow-x-auto max-w-full">
<!-- Tabla de Registros -->
<table class=" table-auto w-full">
<thead class="text-sm text-left py-4 border-b border-gray-300">
<tr class="text-left font-semibold border-collapse">
<th class="py-2 px-4 border-b">Item</th>
<th class="py-2 px-4 border-b">Descripción</th>
<th class="py-2 px-4 border-b">Enlace</th>
<th class="py-2 px-4 border-b">Imagen</th>
<th class="py-2 px-4 border-b w-32"></th>
</tr>
</thead>
<tbody class="text-gray-500 select-none text-sm ">
<!-- Mensaje de Carga Inicial -->
<tr class="hover:bg-gray-100" x-show="isLoading">
<td colspan="" class="py-2 px-4 border-b text-center w-full">
<div class="text-center mx-auto justify-center flex space-x-2">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 mx-1 text-gray-500" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z">
</path>
</svg>
Cargando registros...
</div>
</td>
</tr>
<!-- Itera sobre los roles -->
<template x-for="registro in registros" :key="registro.ID">
<tr class="hover:bg-gray-100" :data-role-id="registro.ID">
<td class="py-3 text-xs px-4 border-b" x-text="registro.ID"></td>
<td class="py-3 text-xs px-4 border-b" x-text="registro.description"></td>
<td class="py-3 text-xs px-4 border-b" x-text="registro.enlace"></td>
<td class="py-3 text-xs px-4 border-b">
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="window.open(`${window.location.origin}/${registro.ruta}`, '_blank')">
Ver imagen
</button>
</td>
<td class="py-3 text-xs px-4 border-b">
<div class="flex justify-between gap-2">
<button
@click="viewModal = true; Description = registro.description; Enlace = registro.enlace; Ruta = registro.ruta;"
title="Ver">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-[#8eb02f]">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<button
@click="editModal = true; Id = registro.ID; Description = registro.description; Enlace = registro.enlace; Ruta = registro.ruta;"
title="Editar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</button>
<button @click="showConfirmModal = true; deleteId = registro.ID;" title="Eliminar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-red-500">
<path stroke-linecap="round" stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button>
</div>
</td>
</tr>
</template>
<tr x-show="sinregistro">
<td colspan="3" class="py-2 px-4 border-b text-center">No hay registros disponibles.</td>
</tr>
</tbody>
</table>
</div>
<div>
<!-- Los botones de paginación se generarán aquí -->
<div id="pagination" class="flex justify-left my-4 text-sm">
</div>
<!-- Mostrar conteo de registros -->
<div id="recordCountDiv" class="text-sm text-gray-700 ml-4">
</div>
</div>
</div>
<!-- Modal Ver -->
<div x-cloak x-show="viewModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Ver registro</h2>
<p class="font-semibold" x-text="Description"></p>
<p class="mt-2" x-text="Enlace"></p>
<p class="mt-2" x-text="Ruta"></p>
<button class="mt-4 bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="viewModal = false">Cerrar</button>
</div>
</div>
<!-- Modal Editar -->
<div x-cloak x-show="editModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Editar registro</h2>
<form @submit.prevent="submitEdit">
<label class="block mt-4 mb-2" for="editDescription">Descripción</label>
<textarea id="editDescription" x-model="Description" placeholder="Descripción"
class="border border-gray-300 rounded w-full p-2" required></textarea>
<label class="block mt-4 mb-2" for="editEnlace">Enlace</label>
<input id="editEnlace" x-model="Enlace" class="border border-gray-300 rounded w-full p-2">
<label class="block mb-2" for="editRuta">Imagen</label>
<input type="file" id="editFile" @change="handleUpdateFileChange"
class="border border-gray-300 rounded w-full p-2" accept="image/*">
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar Cambios</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="editModal = false">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Añadir -->
<div x-cloak x-show="addModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Agregar registro</h2>
<form @submit.prevent="submitAdd">
<label class="block mt-4 mb-2" for="description">Descripción</label>
<textarea id="description" x-model="newDescription" placeholder="Descripción"
class="border border-gray-300 rounded w-full p-2" required></textarea>
<label class="block mt-4 mb-2" for="newEnlace">Enlace</label>
<input id="enlace" x-model="newEnlace" class="border border-gray-300 rounded w-full p-2">
<label class="block mb-2" for="file">Imagen</label>
<input type="file" id="file" @change="handleFileChange"
class="border border-gray-300 rounded w-full p-2" accept="image/*" required>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar registro</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Confirmar Eliminación -->
<div x-cloak x-show="showConfirmModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-4">Confirmar Eliminación</h2>
<p>¿Estás seguro de que deseas eliminar este registro?</p>
<div class="mt-4 flex justify-end">
<button class="bg-red-500 text-white px-4 py-2 rounded mr-2" @click="deleteItem()">Eliminar</button>
<button @click="closeModal" class="bg-gray-600 text-white px-4 py-2 rounded">Cancelar</button>
</div>
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('modals', () => ({
addModal: false,
editModal: false,
viewModal: false,
showConfirmModal: false,
newDescription: '',
newEnlace: '',
newRuta: '',
newFile: null, // Archivo para la carga
Description: '',
Enlace: '',
Ruta: '',
updateFile: null, // Archivo para la actualización
sinregistro: '',
Id: null,
deleteId: null,
loading: false,
totalRecords: 0,
isLoading: false,
registros: '',
init() {
this.loadRegister();
},
searchQuery: '',
loadRegister(page = 1) {
this.isLoading = true;
const search = this.searchQuery ? `&search=${this.searchQuery}` : '';
fetch(`/loadinicio?page=${page}${search}`)
.then(response => response.json())
.then(data => {
this.totalRecords = data.total;
this.registros = data.records;
this.totalPages = data.totalPages;
this.createPagination(this.totalPages, page);
this.sinregistro = data.records.length === 0;
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.isLoading = false;
});
},
createPagination(totalPages, currentPage) {
const paginationContainer = document.getElementById('pagination');
paginationContainer.innerHTML = '';
const paginationButtonsContainer = document.createElement('div');
paginationButtonsContainer.className = "md:flex mx-auto mb-2 md:justify-between items-center w-full";
const createButton = (text, isDisabled, onClick, additionalClasses = '') => {
const button = document.createElement('button');
button.textContent = text;
button.disabled = isDisabled;
button.className = `px-4 py-2 text-black bg-gray-200 ${additionalClasses} ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-300'}`;
if (!isDisabled) {
button.onclick = onClick;
}
return button;
};
const leftContainer = document.createElement('div');
leftContainer.className = "md:flex text-center mb-2";
leftContainer.appendChild(createButton('Anterior', currentPage === 1, () => this.loadRegister(currentPage - 1), 'rounded-l-md'));
for (let i = 1; i <= totalPages; i++) {
const pageButton = createButton(i, false, () => this.loadRegister(i), `mx-1 ${i === currentPage ? 'bg-black text-gray-50' : 'bg-white text-black'} rounded-md`);
leftContainer.appendChild(pageButton);
}
leftContainer.appendChild(createButton('Siguiente', currentPage === totalPages, () => this.loadRegister(currentPage + 1), 'rounded-r-md'));
paginationButtonsContainer.appendChild(leftContainer);
const recordCountDiv = document.createElement('div');
const startRecord = (currentPage - 1) * 10 + 1;
const endRecord = Math.min(currentPage * 10, this.totalRecords);
recordCountDiv.className = "text-sm text-gray-700 text-center md:ml-auto";
recordCountDiv.innerHTML = `Mostrando ${startRecord} a ${endRecord} de ${this.totalRecords} registros`;
paginationButtonsContainer.appendChild(recordCountDiv);
paginationContainer.appendChild(paginationButtonsContainer);
},
closeModal() {
this.addModal = false;
this.editModal = false;
this.viewModal = false;
this.showConfirmModal = false;
this.newFile = null; // Reiniciar archivo
this.updateFile = null; // Reiniciar archivo
},
handleFileChange(event) {
this.newFile = event.target.files[0]; // Manejar el archivo seleccionado
},
handleUpdateFileChange(event) {
this.updateFile = event.target.files[0]; // Manejar el archivo seleccionado para la actualización
},
submitAdd() {
this.loading = true;
const formData = new FormData();
formData.append('description', this.newDescription);
formData.append('enlace', this.newEnlace);
if (this.newFile) formData.append('file', this.newFile); // Adjuntar el archivo si existe
fetch('/app/inicio', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
this.newDescription = '';
this.newEnlace = '';
this.newRuta = data.ruta; // Guardar la ruta del archivo en la respuesta
this.newFile = null; // Reinicia la variable de archivo
// Limpia manualmente el input de archivo en el DOM
document.getElementById('file').value = '';
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
submitEdit() {
this.loading = true;
const formData = new FormData();
formData.append('description', this.Description);
formData.append('enlace', this.Enlace);
if (this.updateFile) {
// Adjuntar el archivo si existe
formData.append('file', this.updateFile);
} else if (this.Ruta) {
// Adjuntar el string 'Ruta' si no existe el archivo
formData.append('file', this.Ruta);
}
fetch(`/app/inicio/${this.Id}`, {
method: 'PUT',
body: formData
})
.then(response => response.json())
.then(data => {
this.Description = '';
this.Enlace = '';
this.Ruta = data.ruta; // Actualizar la ruta del archivo en la respuesta
this.updateFile = null; // Reinicia la variable de archivo
// Limpia manualmente el input de archivo en el DOM
document.getElementById('editFile').value = '';
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
deleteItem() {
this.loading = true;
fetch(`/app/inicio/${this.deleteId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
.then(() => {
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
}
}));
});
</script>
</div>
View File
+57
View File
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>Main</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style>
.profile-action:hover .profile-action-dropdown {
display: inline-block;
}
aside ul li:hover .menu-item-text {
display: inline-block;
position: absolute;
margin-left: 7rem;
width: 10rem;
padding: 22px;
background-color: rgba(247, 250, 252, var(--bg-opacity));
}
aside .logout:hover .menu-item-text {
display: inline-block;
padding: 22px;
position: absolute;
margin-left: 7rem;
width: 10rem;
}
.date:after {
content: "";
position: absolute;
border-top: 1px solid #e2e8f0;
top: 12px;
width: 150px;
}
.date:after {
margin-left: 15px;
}
</style>
</head>
<body class="bg-gray-200 h-screen w-screen flex">
<div class="flex-1 flex flex-col" x-data="{ open: false }">
<button class="bg-blue-500 text-white p-4" @click="open = !open">Toggle Dropdown</button>
<div x-show="open" class="mt-2 p-4 bg-white shadow-lg">
Dropdown Content
</div>
{{template "partials/landing-header" .}}
{{embed}}
</div>
</body>
</html>
+66
View File
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="/css/vendor.css">
<link rel="stylesheet" href="/css/app.css">
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.email-container {
background-color: #ffffff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 600px;
margin: auto;
}
.email-header {
font-size: 24px;
font-weight: bold;
color: #333333;
margin-bottom: 10px;
}
.email-content {
font-size: 16px;
color: #555555;
line-height: 1.5;
}
.email-button {
display: inline-block;
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
color: #ffffff;
background-color: #007bff;
text-decoration: none;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
transition: background-color 0.3s;
}
.email-button:hover {
background-color: #0056b3;
}
.footer-note {
margin-top: 20px;
font-size: 14px;
color: #999999;
}
</style>
<head>
<title>Main</title>
</head>
<body>
{{embed}}
</body>
</html>
View File
+66
View File
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FACTURA</title>
<!-- Tailwind CSS -->
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<!-- PDF.js -->
<script src="https://cdn.jsdelivr.net/npm/qrcode/build/qrcode.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.0/dist/JsBarcode.all.min.js"></script>
<style>
#pdf-container {
overflow-x: hidden;
}
canvas {
width: 100% !important;
height: auto !important;
}
#Modalexto {
position: absolute;
display: none;
}
.notification-hidden {
visibility: hidden;
opacity: 0;
transition: opacity 0.5s, visibility 0.5s;
}
.notification-visible {
visibility: visible;
opacity: 1;
transition: opacity 0.5s, visibility 0.5s;
}
</style>
</head>
<style>
[x-cloak] {
display: none !important;
}
</style>
</head>
<body class="bg-gray-100 justify-center items-center min-h-screen">
<main class="flex justify-between w-full">
<div class="w-full">
{{embed}}
</div>
</div>
</main>
</body>
</html>
+31
View File
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gases del Oriente</title>
<!-- Alpine.js -->
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<!-- TailwindCSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Select2 CSS -->
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<!-- Select2 JS -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<style>
[x-cloak] {
display: none !important;
}
</style>
</head>
<body>
<main class="flex space-x-1 justify-between">
<div class="w-full">
{{embed}}
</div>
</main>
</body>
</html>
+137
View File
@@ -0,0 +1,137 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gases del Oriente</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<script src="https://cdn.tailwindcss.com"></script>
<!-- Cargar jQuery primero -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Luego cargar Select2 -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<style>
[x-cloak] {
display: none !important;
}
</style>
</head>
<body class="bg-gray-200 w-full">
<main class="flex justify-between w-full">
<!-- Componente Lateral (Barra Lateral) -->
{{template "partials/lateral" .}}
<div class="w-full">
{{template "partials/header" .}}
<div class="w-full px-3 py-3 h-[calc(100vh-64px)] overflow-auto">
{{embed}}
</div>
{{template "partials/footer" .}}
</div>
</main>
<div x-data="sessionTimeout" x-init="init()" x-cloak class="hidden sm:block">
<!-- Contador General de Inactividad -->
<div x-show="!showModal" class="fixed top-2 right-20 border text rounded-lg p-2 bg-gray-100 text-sm">
<p>Tiempo restante: <span x-text="formatTime(inactivityCountdown)"></span></p>
</div>
<!-- Modal -->
<div x-show="showModal" class="fixed inset-0 flex items-center justify-center bg-gray-800 bg-opacity-75">
<div class="bg-white rounded-lg shadow-md p-8 w-[600px] relative">
<h2 class="text-xl font-bold mb-6 text-center">¿Deseas continuar?</h2>
<p class="text-gray-600 mb-6 text-center text-base">
Has estado inactivo. Elige "Continuar" para seguir con tu sesión o "Cerrar sesión" para salir.
</p>
<!-- Contador del tiempo para responder -->
<p class="text-center text-red-500 font-semibold mb-4">
Tiempo para responder: <span x-text="countdown"></span> seg
</p>
<div class="flex justify-around">
<button @click="continueSession"
class="px-6 py-3 bg-[#8eb02f] text-white text-base rounded-lg hover:bg-[#698126]">
Continuar
</button>
<form action="/do/logout" method="POST" class="inline-block">
<button type="submit"
class="px-6 py-3 bg-gray-500 text-white text-base rounded-lg hover:bg-gray-600">
Cerrar sesión
</button>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("alpine:init", () => {
Alpine.data("sessionTimeout", () => ({
showModal: false,
countdown: 60, // 10 segundos para responder
inactivityCountdown: 300, // 5 minutos
inactivityTimer: null,
timer: null,
init() {
this.resetInactivityTimer();
// Detectar actividad
window.addEventListener("mousemove", () => this.resetInactivityTimer());
window.addEventListener("keydown", () => this.resetInactivityTimer());
},
resetInactivityTimer() {
clearTimeout(this.inactivityTimer);
this.inactivityCountdown = 300; // Reiniciar contador a 5 minutos
this.startInactivityTimer();
},
startInactivityTimer() {
this.inactivityTimer = setInterval(() => {
this.inactivityCountdown--;
if (this.inactivityCountdown <= 0) {
clearInterval(this.inactivityTimer);
this.promptLogout();
}
}, 1000);
},
promptLogout() {
this.showModal = true;
this.startCountdown();
},
startCountdown() {
this.countdown = 60; // Reiniciar contador del modal
clearInterval(this.timer);
this.timer = setInterval(() => {
this.countdown--;
if (this.countdown <= 0) {
this.logout();
}
}, 1000);
},
continueSession() {
clearInterval(this.timer);
this.showModal = false;
this.resetInactivityTimer();
},
logout() {
clearInterval(this.timer);
document.querySelector('form[action="/do/logout"]').submit();
},
formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
},
}));
});
</script>
</body>
</html>
+151
View File
@@ -0,0 +1,151 @@
<div x-data="modals" @keydown.escape="closeModal" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full">
<h1 class="text-2xl font-bold mb-4">PQRs</h1>
<div class="justify-between w-full md:flex">
<p class="mb-4 text-sm">Relación de PQRs.</p>
</div>
<!-- GIF de carga -->
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<!-- Mostrar registros obtenidos -->
<div x-show="!loading && records.length > 0" class="overflow-x-auto mt-4">
<div class="grid grid-cols-2 gap-4">
<template x-for="(record, index) in records" :key="index">
<table class="min-w-full border-collapse border border-gray-200 rounded-lg overflow-hidden shadow-sm bg-gray-100 text-sm">
<tbody>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">No. Solicitud</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.No_Solicitud"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Fecha</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="formatDate(record.Fecha)"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Hora</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="formatTime(record.Hora)"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Clase</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Clase.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Causal</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Causal.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Detalle</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Detalle.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Factura</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Factura.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Estado</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Estado.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Observación</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Observacion.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Fecha Solución</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="formatDate(record.Fecha_Sol)"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Hora Solución</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="formatTime(record.Hora_Sol)"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Solución</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Solucion.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Detalle Solución</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.DetalleSol.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Lectura</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Lectura.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Observaciones Sol</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Observaciones_Sol.trim() || '-'"></td>
</tr>
</tbody>
</table>
</template>
</div>
</div>
<!-- Mensaje si no hay registros -->
<div x-show="!loading && records.length === 0" class="text-center mt-4 text-gray-500">
No se encontraron registros.
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('modals', () => ({
usuario: '{{ .user.NombreUsuario }}',
records: [],
loading: false,
init() {
console.log('init llamado');
this.loadRegister();
},
async loadRegister() {
try {
console.log('loadRegister llamado');
this.loading = true;
const response = await fetch(`/app/loadpqrsicom/${this.usuario}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Error al cargar registros: ${response.statusText}`);
}
const data = await response.json();
console.log('Registros obtenidos:', data);
this.records = data?.data || [];
} catch (error) {
console.error('Error al cargar los registros:', error);
this.records = [];
} finally {
this.loading = false;
}
},
formatDate(date) {
return new Date(date).toLocaleDateString('es-CO', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
},
formatTime(time) {
const [hour, minute] = time.slice(11, 16).split(':');
return `${hour}:${minute}`;
},
closeModal() {
this.loading = false;
},
}));
});
</script>
</div>
+24
View File
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{.title}} Verify.Rest | Validate, Clean and Analyze </title>
<link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png">
<link rel="short icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png">
</head>
<body class="flex flex-col min-h-screen ">
{{template "partials/landing/header" .}}
<div id="body-wrapper" class="flex-grow min-h-screen mt-32">
{{embed}}
</div>
{{template "partials/landing/footer" .}}
<link rel="stylesheet" href="/css/vendor.css">
<link rel="stylesheet" href="/css/landing.css">
<script defer id="slcLiveChat" src="https://widget.sonetel.com/SonetelWidget.min.js" data-account-id="207431552"></script>
</body>
</html>
+348
View File
@@ -0,0 +1,348 @@
<!-- Container Principal -->
<div x-data="modals" @keydown.escape="closeModal" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full ">
<h1 class="text-2xl font-bold mb-4">Lista de Módulos</h1>
<div class="justify-between w-full md:flex">
<p class="mb-4 text-sm">Gestión de modulos.</p>
<!-- Campo de Búsqueda -->
<div class="mb-4 relative">
<input type="text" x-model="searchQuery" placeholder="Buscar..."
class="border border-gray-300 rounded w-full md:w-60 p-2 text-sm" @input="loadModules()">
<!-- Contenedor para el botón o el ícono de carga -->
<div x-show="isLoading" class="absolute right-2 top-2 flex items-center">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
</svg>
</div>
</div>
<!-- Botón Nuevo Módulo -->
<div class="flex justify-end mb-4">
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded text-sm"
x-on:click="addModal=true">Nuevo</button>
</div>
</div>
<!-- Agregar el GIF de carga -->
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<div class="overflow-x-auto max-w-full">
<!-- Tabla de Módulos -->
<table class=" table-auto w-full">
<thead class="text-sm text-left py-4 border-b border-gray-300">
<tr class="text-left font-semibold border-collapse">
<th class="py-2 px-4 border-b w-52 ">Título</th>
<th class="py-2 px-4 border-b">Descripción</th>
<th class="py-2 px-4 border-b w-32"></th>
</tr>
</thead>
<tbody id="modulesList" class="text-gray-500 select-none text-sm ">
<!-- Mensaje de Carga Inicial -->
<tr class="hover:bg-gray-100">
<td colspan="" class="py-2 px-4 border-b text-center w-full">
<div class="text-center mx-auto justify-center flex space-x-2">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 mx-1 text-gray-500" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z">
</path>
</svg>
Cargando registros...
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div>
<!-- Los botones de paginación se generarán aquí -->
<div id="pagination" class="flex justify-left my-4 text-sm">
</div>
<!-- Mostrar conteo de registros -->
<div id="recordCountDiv" class="text-sm text-gray-700 ml-4">
</div>
</div>
</div>
<!-- Modal Ver -->
<div x-cloak x-show="viewModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Ver registro</h2>
<p class="font-semibold" x-text="Title"></p>
<p class="mt-2" x-text="Description"></p>
<button class="mt-4 bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="viewModal = false">Cerrar</button>
</div>
</div>
<!-- Modal Editar -->
<div x-cloak x-show="editModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Editar registro</h2>
<form @submit.prevent="submitEditModule">
<label class="block mb-2" for="editTitle">Título</label>
<input type="text" id="editTitle" x-model="Title" placeholder="Título"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="editDescription">Descripción</label>
<textarea id="editDescription" x-model="Description" placeholder="Descripción"
class="border border-gray-300 rounded w-full p-2" required></textarea>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar Cambios</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Añadir -->
<div x-cloak x-show="addModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Agregar registro</h2>
<form @submit.prevent="submitAddModule">
<label class="block mb-2" for="title">Título</label>
<input type="text" id="title" x-model="newTitle" placeholder="Título"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="description">Descripción</label>
<textarea id="description" x-model="newDescription" placeholder="Descripción"
class="border border-gray-300 rounded w-full p-2" required></textarea>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar registro</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Confirmar Eliminación -->
<div x-cloak x-show="showConfirmModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-4">Confirmar Eliminación</h2>
<p>¿Estás seguro de que deseas eliminar este registro?</p>
<div class="mt-4 flex justify-end">
<button @click="deleteModule()" class="bg-red-500 text-white px-4 py-2 rounded mr-2">Eliminar</button>
<button @click="closeModal" class="bg-gray-600 text-white px-4 py-2 rounded">Cancelar</button>
</div>
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('modals', () => ({
addModal: false,
editModal: false,
viewModal: false,
showConfirmModal: false,
newTitle: '',
newDescription: '',
Title: '',
Description: '',
Id: null,
deleteId: null,
loading: false,
totalRecords: 0, // Agregar esta línea
isLoading: false,
init() {
this.loadModules();
},
//CARGAR MODULOS
searchQuery: '', // Agrega esta línea dentro del objeto Alpine
// Modifica la función loadModules para incluir la búsqueda
loadModules(page = 1) {
this.isLoading = true;
//this.loading = true;
const search = this.searchQuery ? `&search=${this.searchQuery}` : '';
fetch(`/app/loadmodules?page=${page}${search}`)
.then(response => response.json())
.then(data => {
this.totalRecords = data.total; // Establece el total de registros aquí
this.modules = data.modules; // Asignar módulos
this.totalPages = data.totalPages; // Asignar total de páginas
this.createPagination(this.totalPages, page);
const modulesList = document.getElementById('modulesList');
modulesList.innerHTML = ''; // Limpiar la lista actual
if (data.modules.length === 0) {
modulesList.innerHTML = `<tr><td colspan="3" class="py-2 px-4 border-b text-center">No hay módulos disponibles.</td></tr>`;
} else {
data.modules.forEach(module => {
const row = document.createElement('tr');
row.setAttribute('data-module-id', module.ID);
row.classList.add('hover:bg-gray-100')
row.innerHTML = `
<td class="py-3 text-xs px-4 border-b ">${module.title}</td>
<td class="py-3 text-xs px-4 border-b ">${module.description}</td>
<td class="py-3 text-xs px-4 border-b flex justify-between space-x-1">
<button x-on:click="viewModal = true; Title = '${module.title}'; Description = '${module.description}'; " title="Ver">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5 text-[#8eb02f]">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<button x-on:click="editModal = true; Id = '${module.ID}'; Title = '${module.title}'; Description = '${module.description}';" title="Editar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</button>
<button x-on:click="showConfirmModal = true; deleteId = '${module.ID}';" title="Eliminar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5 text-red-500">
<path stroke-linecap="round" stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button>
</td>
`;
modulesList.appendChild(row);
});
// Lógica de paginación
const totalPages = Math.ceil(this.totalRecords / data.limit);
this.createPagination(totalPages, page)
}
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.isLoading = false; // Ocultar el GIF de carga
});
},
createPagination(totalPages, currentPage) {
const paginationContainer = document.getElementById('pagination');
paginationContainer.innerHTML = ''; // Limpiar la paginación actual
// Crear un contenedor para los botones de paginación
const paginationButtonsContainer = document.createElement('div');
paginationButtonsContainer.className = "md:flex mx-auto mb-2 md:justify-between items-center w-full"; // Alinear los botones
// Función para crear un botón
const createButton = (text, isDisabled, onClick, additionalClasses = '') => {
const button = document.createElement('button');
button.textContent = text;
button.disabled = isDisabled;
button.className = `px-4 py-2 text-black bg-gray-200 ${additionalClasses} ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-300'}`;
if (!isDisabled) {
button.onclick = onClick;
}
return button;
};
// Botones de paginación
const leftContainer = document.createElement('div');
leftContainer.className = "md:flex text-center mb-2"; // Contenedor para los botones de paginación a la izquierda
leftContainer.appendChild(createButton('Anterior', currentPage === 1, () => this.loadModules(currentPage - 1), 'rounded-l-md'));
for (let i = 1; i <= totalPages; i++) {
const pageButton = createButton(i, false, () => this.loadModules(i), `mx-1 ${i === currentPage ? 'bg-black text-gray-50' : 'bg-white text-black'} rounded-md`);
leftContainer.appendChild(pageButton);
}
leftContainer.appendChild(createButton('Siguiente', currentPage === totalPages, () => this.loadModules(currentPage + 1), 'rounded-r-md'));
paginationButtonsContainer.appendChild(leftContainer);
// Agregar el conteo de registros a la derecha
const recordCountDiv = document.createElement('div');
const startRecord = (currentPage - 1) * 10 + 1; // Primer registro de la página actual
const endRecord = Math.min(currentPage * 10, this.totalRecords); // Último registro mostrado
recordCountDiv.className = "text-sm text-gray-700 text-center md:ml-auto"; // Sin margen para alineación a la izquierda
recordCountDiv.innerHTML = `Mostrando ${startRecord} a ${endRecord} de ${this.totalRecords} registros`;
paginationButtonsContainer.appendChild(recordCountDiv);
// Agregar los botones de paginación al contenedor principal
paginationContainer.appendChild(paginationButtonsContainer);
},
//CERRAR MODALES
closeModal() {
this.addModal = false;
this.editModal = false;
this.viewModal = false;
this.showConfirmModal = false;
},
//CREAR
submitAddModule() {
this.loading = true;
fetch('/app/modules', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: this.newTitle, description: this.newDescription })
})
.then(response => response.json())
.then(() => {
this.newTitle = '';
this.newDescription = '';
this.closeModal();
this.loadModules();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
//ACTUALIZAR
submitEditModule() {
this.loading = true;
fetch(`/app/modules/${this.Id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: this.Title, description: this.Description })
})
.then(response => response.json())
.then(() => {
this.newTitle = '';
this.newDescription = '';
this.closeModal();
this.loadModules();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
//ELIMINAR
deleteModule() {
this.loading = true;
fetch(`/app/modules/${this.deleteId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
.then(() => {
this.closeModal();
this.loadModules();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
}
}));
});
</script>
</div>
+375
View File
@@ -0,0 +1,375 @@
<!-- Container Principal -->
<div x-data="parametrizacionForm" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full">
<h1 class="text-2xl font-bold mb-4">Parametrización del sitio web</h1>
<p class="mb-4 text-sm">Gestión de parametrización del sitio web</p>
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<!-- Inicio del contenedor de grid -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Sección General -->
<div class="p-6 rounded-lg border w-full shadow">
<h2 class="text-xl font-bold mb-2">General</h2>
<!-- Contenido de la sección General -->
<label class="block mb-2" for="logo">Logo</label>
<input type="file" id="logo" @change="handleFileChange($event, 'logo')"
class="border border-gray-300 rounded w-full p-2 mb-2" accept="image/*">
<p x-show="logo_path" class="mb-2 text-sm text-gray-600">
Logo actual: <a :href="`/${logo_path}`" target="_blank" class="text-blue-500 underline"
x-text="logo_path.split('/').pop()"></a>
</p>
<!--<label class="block mb-2" for="video_institucional">Video institucional</label>
<input type="file" id="video_institucional" @change="handleFileChange($event, 'video_institucional')" class="border border-gray-300 rounded w-full p-2 mb-2" accept="video/*">
<p x-show="video_institucional_path" class="mb-2 text-sm text-gray-600">
Logo actual: <a :href="`/${video_institucional_path}`" target="_blank" class="text-blue-500 underline" x-text="video_institucional_path.split('/').pop()"></a>
</p>-->
<label class="block mb-2" for="descripcion_seo">Descripción SEO</label>
<textarea type="text" id="descripcion_seo" x-model="descripcion_seo"
placeholder="Ingrese la descripción SEO"
class="border border-gray-300 rounded w-full p-2 mb-4"></textarea>
<label class="block mb-2" for="email">Correo electrónico</label>
<input type="email" id="email" x-model="email" placeholder="Ingrese el correo electrónico"
class="border border-gray-300 rounded w-full p-2 mb-4">
<label class="block mb-2" for="enlace_pagar_ahora">Enlace de Pagar Ahora</label>
<input type="url" id="enlace_pagar_ahora" x-model="enlace_pagar_ahora"
placeholder="Ingrese el enlace de pago" class="border border-gray-300 rounded w-full p-2 mb-4">
<label class="block mb-2" for="imagen_resena">Imagen de la Reseña</label>
<input type="file" id="imagen_resena" @change="handleFileChange($event, 'imagen_resena')"
class="border border-gray-300 rounded w-full p-2 mb-4" accept="image/*">
<!-- Mostrar imagen actual de la reseña -->
<p x-show="imagen_resena_path" class="mb-2 text-sm text-gray-600">
Imagen actual: <a :href="`/${imagen_resena_path}`" target="_blank" class="text-blue-500 underline"
x-text="imagen_resena_path.split('/').pop()"></a>
</p>
<!-- Botón de Guardar para esta sección -->
<div class="mt-4 text-right">
<button @click.prevent="submitUpdateGeneral"
class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar General</button>
</div>
</div>
<!-- Sección Documentos -->
<div class="p-6 rounded-lg border w-full shadow">
<h2 class="text-xl font-bold mb-2">Documentos</h2>
<!-- Contenido de la sección Documentos -->
<label class="block mb-2" for="contrato_condiciones">Contrato Condiciones</label>
<input type="file" id="contrato_condiciones" @change="handleFileChange($event, 'contrato_condiciones')"
class="border border-gray-300 rounded w-full p-2 mb-2" accept=".pdf">
<!-- Mostrar contrato de condiciones actual -->
<p x-show="contrato_condiciones_path" class="mb-2 text-sm text-gray-600">
Archivo actual: <a :href="`/${contrato_condiciones_path}`" target="_blank"
class="text-blue-500 underline" x-text="contrato_condiciones_path.split('/').pop()"></a>
</p>
<label class="block mb-2" for="proteccion_datos">Protección de Datos</label>
<input type="file" id="proteccion_datos" @change="handleFileChange($event, 'proteccion_datos')"
class="border border-gray-300 rounded w-full p-2 mb-2" accept=".pdf">
<!-- Mostrar protección de datos actual -->
<p x-show="proteccion_datos_path" class="mb-2 text-sm text-gray-600">
Archivo actual: <a :href="`/${proteccion_datos_path}`" target="_blank"
class="text-blue-500 underline" x-text="proteccion_datos_path.split('/').pop()"></a>
</p>
<label class="block mb-2" for="contrato_condiciones_descripcion">Descripción de Condiciones</label>
<input type="text" id="contrato_condiciones_descripcion" x-model="contrato_condiciones_descripcion"
placeholder="Ingrese la descripción" class="border border-gray-300 rounded w-full p-2 mb-4">
<label class="block mb-2" for="proteccion_datos_descripcion">Descripción de Protección de Datos</label>
<input type="text" id="proteccion_datos_descripcion" x-model="proteccion_datos_descripcion"
placeholder="Ingrese la descripción" class="border border-gray-300 rounded w-full p-2 mb-4">
<!-- Botón de Guardar para esta sección -->
<div class="mt-4 text-right">
<button @click.prevent="submitUpdateDocumentos"
class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar Documentos</button>
</div>
</div>
<!-- Sección Redes Sociales -->
<div class="p-6 rounded-lg border w-full shadow">
<h2 class="text-xl font-bold mb-2">Redes Sociales</h2>
<!-- Contenido de la sección Redes Sociales -->
<label class="block mb-2" for="enlace_facebook">Enlace Facebook</label>
<input type="url" id="enlace_facebook" x-model="enlace_facebook"
placeholder="Ingrese el enlace de Facebook" class="border border-gray-300 rounded w-full p-2 mb-4">
<label class="block mb-2" for="enlace_instagram">Enlace Instagram</label>
<input type="url" id="enlace_instagram" x-model="enlace_instagram"
placeholder="Ingrese el enlace de Instagram" class="border border-gray-300 rounded w-full p-2 mb-4">
<label class="block mb-2" for="enlace_whatsapp">Whatsapp Principal</label>
<input type="url" id="enlace_whatsapp" x-model="enlace_whatsapp"
placeholder="Ingrese el número de Whatsapp" class="border border-gray-300 rounded w-full p-2 mb-4">
<!-- Campo Select para Estado -->
<label class="block mb-2" for="estado_redes">Estado</label>
<select id="estado_redes" x-model="estado_redes" class="border border-gray-300 rounded w-full p-2 mb-4">
<option value="true">Activo</option>
<option value="false">Inactivo</option>
</select>
<!-- Botón de Guardar para esta sección -->
<div class="mt-4 text-right">
<button @click.prevent="submitUpdateRedes" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar
Redes Sociales</button>
</div>
</div>
<!-- Sección FNB (ocupa el ancho completo)
<div class="p-6 rounded-lg border w-full shadow">
<h2 class="text-xl font-bold mb-2">FNB</h2>
<label class="block mb-2" for="email_notificaciones_fnb">Email Notificaciones FNB</label>
<input type="email" id="email_notificaciones_fnb" x-model="email_notificaciones_fnb" placeholder="Ingrese el correo" class="border border-gray-300 rounded w-full p-2 mb-2">
<label class="block mb-2" for="descripcion_fnb">Descripción</label>
<textarea type="text" id="descripcion_fnb" x-model="descripcion_fnb" placeholder="Ingrese la descripción FNB" class="border border-gray-300 rounded w-full p-2 mb-4"></textarea>
<label class="block mb-2" for="donde_comprar_fnb">Dónde Comprar FNB</label>
<input type="file" id="donde_comprar_fnb" @change="handleFileChange($event, 'donde_comprar_fnb')" class="border border-gray-300 rounded w-full p-2 mb-4" accept=".pdf">
<p x-show="donde_comprar_fnb_path" class="mb-2 text-sm text-gray-600">
Archivo actual: <a :href="`/${donde_comprar_fnb_path}`" target="_blank" class="text-blue-500 underline" x-text="donde_comprar_fnb_path.split('/').pop()"></a>
</p>
<div class="mt-4 text-right">
<button @click.prevent="submitUpdateFNB" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar FNB</button>
</div>
</div>-->
<!-- Sección FNB (ocupa el ancho completo) -->
<div class="p-6 rounded-lg border w-full shadow">
<h2 class="text-xl font-bold mb-2">Importancia del pago</h2>
<!-- Contenido de la sección FNB -->
<label class="block mb-2" for="descripcion">Descripción</label>
<textarea type="text" id="descripcion" x-model="descripcion" placeholder="Ingrese la descripción SEO"
class="border border-gray-300 rounded w-full p-2 mb-4"></textarea>
<label class="block mb-2" for="descripcion_cuadro_azul">Descripción del cuadro azul</label>
<textarea type="text" id="descripcion_cuadro_azul" x-model="descripcion_cuadro_azul"
placeholder="Ingrese la descripción SEO"
class="border border-gray-300 rounded w-full p-2 mb-4"></textarea>
<!-- Botón de Guardar para esta sección -->
<div class="mt-4 text-right">
<button @click.prevent="submitUpdateImportancia"
class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar importancia del Pago</button>
</div>
</div>
</div>
<!-- Fin del contenedor de grid -->
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('parametrizacionForm', () => ({
logo: null,
descripcion_seo: '',
email: '',
enlace_pagar_ahora: '',
contrato_condiciones: null,
proteccion_datos: null,
contrato_condiciones_descripcion: '',
proteccion_datos_descripcion: '',
enlace_facebook: '',
enlace_instagram: '',
enlace_whatsapp: '',
email_notificaciones_fnb: '',
donde_comprar_fnb: null,
loading: false,
logo_path: '',
video_institucional: null,
video_institucional_path: '',
contrato_condiciones_path: '',
proteccion_datos_path: '',
donde_comprar_fnb_path: '',
descripcion: '',
descripcion_cuadro_azul: '',
descripcion_fnb: '',
estado_redes: true,
imagen_resena: null,
imagen_resena_path: '',
id_dato: 0,
init() {
this.loadData();
},
loadData() {
this.loading = true;
fetch('/loadparametrizacionweb')
.then(response => response.json())
.then(data => {
if (data) {
this.id_dato = data.ID;
this.descripcion_seo = data.descripcion_seo;
this.email = data.email;
this.enlace_pagar_ahora = data.enlace_pagar_ahora;
this.contrato_condiciones_descripcion = data.contrato_condiciones_descripcion;
this.proteccion_datos_descripcion = data.proteccion_datos_descripcion;
this.enlace_facebook = data.enlace_facebook;
this.enlace_instagram = data.enlace_instagram;
this.enlace_whatsapp = data.enlace_whatsapp;
this.email_notificaciones_fnb = data.email_notificaciones_fnb;
this.logo_path = data.logo;
this.video_institucional_path = data.video_institucional;
this.contrato_condiciones_path = data.contrato_condiciones;
this.proteccion_datos_path = data.proteccion_datos;
this.donde_comprar_fnb_path = data.donde_comprar_fnb;
this.descripcion = data.descripcion;
this.descripcion_cuadro_azul = data.descripcion_cuadro_azul;
this.descripcion_fnb = data.descripcion_fnb;
this.estado_redes = data.estado_redes;
this.imagen_resena_path = data.imagen_resena;
}
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
handleFileChange(event, field) {
if (field === 'imagen_resena') {
this.imagen_resena = event.target.files[0];
} else if (field === 'logo') {
this.logo = event.target.files[0];
} else if (field === 'contrato_condiciones') {
this.contrato_condiciones = event.target.files[0];
} else if (field === 'proteccion_datos') {
this.proteccion_datos = event.target.files[0];
} else if (field === 'donde_comprar_fnb') {
this.donde_comprar_fnb = event.target.files[0];
} else if (field === 'video_institucional') {
this.video_institucional = event.target.files[0];
}
},
// Funciones de actualización para cada sección
submitUpdateGeneral() {
this.loading = true;
const formData = new FormData();
formData.append('descripcion_seo', this.descripcion_seo);
formData.append('email', this.email);
formData.append('enlace_pagar_ahora', this.enlace_pagar_ahora);
if (this.logo) formData.append('logo', this.logo);
//if (this.video_institucional) formData.append('video_institucional', this.video_institucional);
if (this.imagen_resena) formData.append('imagen_resena', this.imagen_resena);
fetch('/app/parametrizacion-web/'+this.id_dato, {
method: 'PUT',
body: formData
})
.then(response => response.json())
.then(() => {
this.loadData();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
submitUpdateDocumentos() {
this.loading = true;
const formData = new FormData();
formData.append('contrato_condiciones_descripcion', this.contrato_condiciones_descripcion);
formData.append('proteccion_datos_descripcion', this.proteccion_datos_descripcion);
if (this.contrato_condiciones) formData.append('contrato_condiciones', this.contrato_condiciones);
if (this.proteccion_datos) formData.append('proteccion_datos', this.proteccion_datos);
fetch('/app/parametrizacion-web/'+this.id_dato, {
method: 'PUT',
body: formData
})
.then(response => response.json())
.then(() => {
this.loadData();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
submitUpdateRedes() {
this.loading = true;
const formData = new FormData();
formData.append('enlace_facebook', this.enlace_facebook);
formData.append('enlace_instagram', this.enlace_instagram);
formData.append('enlace_whatsapp', this.enlace_whatsapp);
formData.append('estado_redes', this.estado_redes);
fetch('/app/parametrizacion-web/'+this.id_dato, {
method: 'PUT',
body: formData
})
.then(response => response.json())
.then(() => {
this.loadData();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
submitUpdateFNB() {
this.loading = true;
const formData = new FormData();
formData.append('email_notificaciones_fnb', this.email_notificaciones_fnb);
formData.append('descripcion_fnb', this.descripcion_fnb);
if (this.donde_comprar_fnb) formData.append('donde_comprar_fnb', this.donde_comprar_fnb);
fetch('/app/parametrizacion-web/'+this.id_dato, {
method: 'PUT',
body: formData
})
.then(response => response.json())
.then(() => {
this.loadData();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
submitUpdateImportancia() {
this.loading = true;
const formData = new FormData();
formData.append('descripcion', this.descripcion);
formData.append('descripcion_cuadro_azul', this.descripcion_cuadro_azul);
fetch('/app/parametrizacion-web/'+this.id_dato, {
method: 'PUT',
body: formData
})
.then(response => response.json())
.then(() => {
this.loadData();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
}));
});
</script>
</div>
+294
View File
@@ -0,0 +1,294 @@
<div class="float-right flex-row">
<main class="max-w-screen-lg h-full">
<!-- file upload modal -->
<article aria-label="File Upload Modal" class="relative h-full flex flex-col bg-white shadow-xl rounded-md"
ondrop="dropHandler(event);" ondragover="dragOverHandler(event);"
ondragleave="dragLeaveHandler(event);" ondragenter="dragEnterHandler(event);">
<!-- overlay -->
<div id="overlay"
class="w-full h-full absolute top-0 left-0 pointer-events-none z-50 flex flex-col items-center justify-center rounded-md">
<i>
<svg class="fill-current w-12 h-12 mb-3 text-blue-700" xmlns="http://www.w3.org/2000/svg" width="24"
height="24" viewBox="0 0 24 24">
<path d="M19.479 10.092c-.212-3.951-3.473-7.092-7.479-7.092-4.005 0-7.267 3.141-7.479 7.092-2.57.463-4.521 2.706-4.521 5.408 0 3.037 2.463 5.5 5.5 5.5h13c3.037 0 5.5-2.463 5.5-5.5 0-2.702-1.951-4.945-4.521-5.408zm-7.479-1.092l4 4h-3v4h-2v-4h-3l4-4z"/>
</svg>
</i>
<p class="text-lg text-blue-700">Drop files to upload</p>
</div>
<!-- scroll area -->
<section class="h-full overflow-auto p-8 w-full h-full flex flex-col">
<header class="border-dashed border-2 border-gray-400 py-12 flex flex-col justify-center items-center">
<p class="mb-3 font-semibold text-gray-900 flex flex-wrap justify-center p-2">
<span>Drag and drop your</span>&nbsp;<span>files anywhere or</span>
</p>
<input id="hidden-input" type="file" multiple class="hidden" accept=".csv"/>
<button id="button"
class="mt-2 rounded-sm px-3 py-1 bg-gray-200 hover:bg-gray-300 focus:shadow-outline focus:outline-none">
Upload a file
</button>
</header>
<h1 class="pt-8 pb-3 font-semibold sm:text-lg text-gray-900">
To Upload
</h1>
<ul id="gallery" class="flex flex-1 flex-wrap -m-1">
<li id="empty"
class="h-full w-full text-center flex flex-col items-center justify-center items-center">
<img class="mx-auto w-32"
src="https://user-images.githubusercontent.com/507615/54591670-ac0a0180-4a65-11e9-846c-e55ffce0fe7b.png"
alt="no data"/>
<span class="text-small text-gray-500">No files selected</span>
</li>
</ul>
</section>
<!-- sticky footer -->
<footer class="flex justify-end px-8 pb-8 pt-4">
<button id="submit"
class="rounded-sm px-3 py-1 bg-blue-700 hover:bg-blue-500 text-white focus:shadow-outline focus:outline-none">
Upload now
</button>
<button id="cancel"
class="ml-3 rounded-sm px-3 py-1 hover:bg-gray-300 focus:shadow-outline focus:outline-none">
Cancel
</button>
</footer>
</article>
</main>
</div>
<!-- using two similar templates for simplicity in js code -->
<template id="file-template">
<li class="block p-1 w-full h-24">
<article tabindex="0"
class="group w-full h-full rounded-md focus:outline-none focus:shadow-outline elative bg-gray-100 cursor-pointer relative shadow-sm">
<img alt="upload preview" class="img-preview hidden w-full h-full sticky object-cover rounded-md bg-fixed"/>
<section class="flex flex-col rounded-md text-xs break-words w-full h-full z-20 absolute top-0 py-2 px-3">
<h1 class="flex-1 group-hover:text-blue-800"></h1>
<div class="flex">
<span class="p-1 text-blue-800">
<i>
<svg class="fill-current w-4 h-4 ml-auto pt-1" xmlns="http://www.w3.org/2000/svg" width="24"
height="24" viewBox="0 0 24 24">
<path d="M15 2v5h5v15h-16v-20h11zm1-2h-14v24h20v-18l-6-6z"/>
</svg>
</i>
</span>
<p class="p-1 size text-xs text-gray-700"></p>
<button class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md text-gray-800">
<svg class="pointer-events-none fill-current w-4 h-4 ml-auto" xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 24 24">
<path class="pointer-events-none"
d="M3 6l3 18h12l3-18h-18zm19-4v2h-20v-2h5.711c.9 0 1.631-1.099 1.631-2h5.316c0 .901.73 2 1.631 2h5.711z"/>
</svg>
</button>
</div>
</section>
</article>
</li>
</template>
<template id="image-template">
<li class="block p-1 w-full h-24">
<article tabindex="0"
class="group hasImage w-full h-full rounded-md focus:outline-none focus:shadow-outline bg-gray-100 cursor-pointer relative text-transparent hover:text-white shadow-sm">
<img alt="upload preview" class="img-preview w-full h-full sticky object-cover rounded-md bg-fixed"/>
<section class="flex flex-col rounded-md text-xs break-words w-full h-full z-20 absolute top-0 py-2 px-3">
<h1 class="flex-1"></h1>
<div class="flex">
<span class="p-1">
<i>
<svg class="fill-current w-4 h-4 ml-auto pt-" xmlns="http://www.w3.org/2000/svg" width="24"
height="24" viewBox="0 0 24 24">
<path d="M5 8.5c0-.828.672-1.5 1.5-1.5s1.5.672 1.5 1.5c0 .829-.672 1.5-1.5 1.5s-1.5-.671-1.5-1.5zm9 .5l-2.519 4-2.481-1.96-4 5.96h14l-5-8zm8-4v14h-20v-14h20zm2-2h-24v18h24v-18z"/>
</svg>
</i>
</span>
<p class="p-1 size text-xs"></p>
<button class="delete ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md">
<svg class="pointer-events-none fill-current w-4 h-4 ml-auto" xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 24 24">
<path class="pointer-events-none"
d="M3 6l3 18h12l3-18h-18zm19-4v2h-20v-2h5.711c.9 0 1.631-1.099 1.631-2h5.316c0 .901.73 2 1.631 2h5.711z"/>
</svg>
</button>
</div>
</section>
</article>
</li>
</template>
<script>
const fileTempl = document.getElementById("file-template"),
imageTempl = document.getElementById("image-template"),
empty = document.getElementById("empty");
// use to store pre selected files
let FILES = {};
// check if file is of type image and prepend the initialied
// template to the target element
function addFile(target, file) {
const isImage = file.type.match("image.*"),
objectURL = URL.createObjectURL(file);
const clone = isImage
? imageTempl.content.cloneNode(true)
: fileTempl.content.cloneNode(true);
clone.querySelector("h1").textContent = file.name;
clone.querySelector("li").id = objectURL;
clone.querySelector(".delete").dataset.target = objectURL;
clone.querySelector(".size").textContent =
file.size > 1024
? file.size > 1048576
? Math.round(file.size / 1048576) + "mb"
: Math.round(file.size / 1024) + "kb"
: file.size + "b";
isImage &&
Object.assign(clone.querySelector("img"), {
src: objectURL,
alt: file.name
});
empty.classList.add("hidden");
target.prepend(clone);
FILES[objectURL] = file;
}
const gallery = document.getElementById("gallery"),
overlay = document.getElementById("overlay");
// click the hidden input of type file if the visible button is clicked
// and capture the selected files
const hidden = document.getElementById("hidden-input");
document.getElementById("button").onclick = () => hidden.click();
hidden.onchange = (e) => {
for (const file of e.target.files) {
addFile(gallery, file);
}
};
// use to check if a file is being dragged
const hasFiles = ({dataTransfer: {types = []}}) =>
types.indexOf("Files") > -1;
// use to drag dragenter and dragleave events.
// this is to know if the outermost parent is dragged over
// without issues due to drag events on its children
let counter = 0;
// reset counter and append file to gallery when file is dropped
function dropHandler(ev) {
ev.preventDefault();
for (const file of ev.dataTransfer.files) {
addFile(gallery, file);
overlay.classList.remove("draggedover");
counter = 0;
}
}
// only react to actual files being dragged
function dragEnterHandler(e) {
e.preventDefault();
if (!hasFiles(e)) {
return;
}
++counter && overlay.classList.add("draggedover");
}
function dragLeaveHandler(e) {
1 > --counter && overlay.classList.remove("draggedover");
}
function dragOverHandler(e) {
if (hasFiles(e)) {
e.preventDefault();
}
}
// event delegation to caputre delete events
// fron the waste buckets in the file preview cards
gallery.onclick = ({target}) => {
if (target.classList.contains("delete")) {
const ou = target.dataset.target;
document.getElementById(ou).remove(ou);
gallery.children.length === 1 && empty.classList.remove("hidden");
delete FILES[ou];
}
};
// print all selected files
document.getElementById("submit").onclick = () => {
var data = new FormData();
let files = Object.values(FILES);
for (var x = 0; x < files.length; x++) {
data.append('files', files[x], files[x].name);
}
var request = new XMLHttpRequest();
request.open('post', '/account/file-manager/upload', true);
// upload progress event
request.upload.addEventListener('progress', function (e) {
var percent_complete = (e.loaded / e.total) * 100;
// Percentage of upload completed
console.log(percent_complete);
});
// AJAX request finished event
request.addEventListener('load', function (e) {
clearUploads()
});
request.send(data);
};
// clear entire selection
document.getElementById("cancel").onclick = () => {
clearUploads()
};
function clearUploads() {
while (gallery.children.length > 0) {
gallery.lastChild.remove();
}
FILES = {};
empty.classList.remove("hidden");
gallery.append(empty);
}
</script>
<style>
.hasImage:hover section {
background-color: rgba(5, 5, 5, 0.4);
}
.hasImage:hover button:hover {
background: rgba(5, 5, 5, 0.45);
}
#overlay p,
i {
opacity: 0;
}
#overlay.draggedover {
background-color: rgba(255, 255, 255, 0.7);
}
#overlay.draggedover p,
#overlay.draggedover i {
opacity: 1;
}
.group:hover .group-hover\:text-blue-800 {
color: #2b6cb0;
}
</style>
+120
View File
@@ -0,0 +1,120 @@
<!-- container -->
<nav
class="fixed bottom-0 w-full border bg-white hidden
overflow-x-auto">
<!-- Bottom Icon Navigation Menu -->
<a
href="."
class="flex flex-col flex-grow items-center justify-center
overflow-hidden whitespace-no-wrap text-sm transition-colors
duration-100 ease-in-out hover:bg-gray-200 focus:text-orange-500">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"></polyline>
<path
d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0
2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path>
</svg>
<span class="hidden text-sm capitalize">Inbox</span>
</a>
<a
href="."
class="flex flex-col flex-grow items-center justify-center
overflow-hidden whitespace-no-wrap text-sm transition-colors
duration-100 ease-in-out hover:bg-gray-200 text-orange-500">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-github">
<path
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0
0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77
5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7
0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0
0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
</svg>
<span class="text-sm capitalize">github</span>
</a>
<a
href="."
class="flex flex-col flex-grow items-center justify-center
overflow-hidden whitespace-no-wrap text-sm transition-colors
duration-100 ease-in-out hover:bg-gray-200 focus:text-orange-500">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-bookmark">
<path
d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
</svg>
<span class="hidden text-sm capitalize">bookmark</span>
</a>
<a
href="."
class="flex flex-col flex-grow items-center justify-center
overflow-hidden whitespace-no-wrap text-sm transition-colors
duration-100 ease-in-out hover:bg-gray-200 focus:text-orange-500">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83
2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65
0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0
0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2
2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0
0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0
4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2
0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0
1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1
1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0
0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0
1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0
0-1.51 1z"></path>
</svg>
<span class="hidden text-sm capitalize">Settings</span>
</a>
</nav>
+41
View File
@@ -0,0 +1,41 @@
<nav class="px-1 md:px-4 flex justify-between bg-white h-16 border-b-2" x-data="{
open: false,
}">
<!-- top bar -->
<ul class="flex items-center md:hidden">
<!-- top bar left -->
<div class="items-center justify-start h-16 md:px-10 py-2 object-cover">
<img src="/img/logo.webp" alt="" class="w-20 ml-11 mt-1 md:mt-0 md:ml-0">
</div>
</ul>
<ul class="flex items-center">
<!-- top bar center -->
<li>
<h1 class="mx-2 lg:pl-0 text-gray-700 md:text-sm text-xs" >{{ .user.NombreUsuario }} - {{ .user.Role.Name }}</h1>
</li>
</ul>
<ul class="flex items-center">
<!-- top bar right -->
<li class="h-8 w-8 relative profile-action">
<button @click="open = !open" class="block h-8 w-8 rounded-full overflow-hidden border-1 border-gray-600 focus:outline-none focus:border-white">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-7 h-7">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<!-- dropdown menu -->
<div x-show="open" @click.outside="open = false" class="profile-action-dropdown absolute bg-gray-100 border border-t-0 shadow-xl text-gray-700 rounded-lg overflow-hidden w-48 top-10 right-0 mr-6 z-10">
<a href="/app/profile" class="block px-4 py-2 hover:bg-gray-200">Mi cuenta</a>
<a href="/do/logout" class="block px-4 py-2 hover:bg-gray-200"
onclick="event.preventDefault(); document.getElementById('logout-form-profile').submit();">
Cerrar sesión
</a>
<form id="logout-form-profile" action="/do/logout" method="POST" style="display: none;"></form>
</div>
<!-- dropdown menu end -->
</li>
</ul>
</nav>
@@ -0,0 +1,54 @@
<footer class="text-gray-700 body-font bg-white absolute w-full bottom-0 left-0">
<div class="container mx-auto flex items-center sm:flex-row flex-col">
<a class="flex title-font font-medium items-center md:justify-start justify-center text-gray-900">
<span class="ml-3 text-xl">Boilerplate</span>
</a>
<p class="text-sm text-gray-500 sm:ml-4 sm:pl-4 sm:border-l-2 sm:border-gray-200 sm:py-2 sm:mt-0 mt-4">© 2020
Boilerplate —
<a href="https://twitter.com/Company" class="text-gray-600 ml-1" rel="noopener noreferrer"
target="_blank">@Company</a>
</p>
<p class="text-sm text-gray-500 sm:ml-4 sm:pl-4 sm:border-l-2 sm:border-gray-200 sm:py-2 sm:mt-0 mt-4">
<a href="/faq" class="text-gray-600 ml-1" rel="noopener noreferrer">FAQ</a>
</p>
<p class="text-sm text-gray-500 sm:ml-4 sm:pl-4 sm:border-l-2 sm:border-gray-200 sm:py-2 sm:mt-0 mt-4">
<a href="/contact-us" class="text-gray-600 ml-1" rel="noopener noreferrer" target="_blank">Contact Us</a>
</p>
<p class="text-sm text-gray-500 sm:ml-4 sm:pl-4 sm:border-l-2 sm:border-gray-200 sm:py-2 sm:mt-0 mt-4">
<a href="/about-us" class="text-gray-600 ml-1" rel="noopener noreferrer" target="_blank">About Us</a>
</p>
<p class="text-sm text-gray-500 sm:ml-4 sm:pl-4 sm:border-l-2 sm:border-gray-200 sm:py-2 sm:mt-0 mt-4">
<a href="/feature-request" class="text-gray-600 ml-1" rel="noopener noreferrer" target="_blank">Feature
Request</a>
</p>
<span class="inline-flex sm:ml-auto sm:mt-0 mt-4 justify-center sm:justify-start">
<a class="text-gray-500">
<svg fill="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-5 h-5"
viewBox="0 0 24 24">
<path d="M18 2h-3a5 5 0 00-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 011-1h3z"></path>
</svg>
</a>
<a class="ml-3 text-gray-500">
<svg fill="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-5 h-5"
viewBox="0 0 24 24">
<path d="M23 3a10.9 10.9 0 01-3.14 1.53 4.48 4.48 0 00-7.86 3v1A10.66 10.66 0 013 4s-4 9 5 13a11.64 11.64 0 01-7 2c9 5 20 0 20-11.5a4.5 4.5 0 00-.08-.83A7.72 7.72 0 0023 3z"></path>
</svg>
</a>
<a class="ml-3 text-gray-500">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
class="w-5 h-5" viewBox="0 0 24 24">
<rect width="20" height="20" x="2" y="2" rx="5" ry="5"></rect>
<path d="M16 11.37A4 4 0 1112.63 8 4 4 0 0116 11.37zm1.5-4.87h.01"></path>
</svg>
</a>
<a class="ml-3 text-gray-500">
<svg fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="0"
class="w-5 h-5" viewBox="0 0 24 24">
<path stroke="none"
d="M16 8a6 6 0 016 6v7h-4v-7a2 2 0 00-2-2 2 2 0 00-2 2v7h-4v-7a6 6 0 016-6zM2 9h4v12H2z"></path>
<circle cx="4" cy="4" r="2" stroke="none"></circle>
</svg>
</a>
</span>
</div>
</footer>
@@ -0,0 +1,58 @@
<nav class="p-6 flex justify-between bg-white h-16 border-b-2">
<!-- top bar -->
<ul class="flex items-center lg:hidden">
<!-- top bar left -->
<li class="h-6 w-6">
<img
class="h-full w-full mx-auto"
src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Svelte_Logo.svg/512px-Svelte_Logo.svg.png"
alt="svelte logo"/>
</li>
</ul>
<ul class="flex items-center">
<!-- top bar center -->
<li>
<h1 class="pl-10 lg:pl-0 text-gray-700">
<a href="/">Boilerplate</a>
</h1>
</li>
</ul>
<ul class="flex items-center">
{{if .auth}}
<li class="h-8 w-8 profile-action">
<button class="block h-8 w-8 rounded-full overflow-hidden border-1 border-gray-600 focus:outline-none focus:border-white">
<img class="h-full w-full object-cover"
src="https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=256&q=80"
alt="Your avatar">
</button>
<!-- dropdown menu -->
<div class="profile-action-dropdown absolute bg-gray-100 border border-t-0 shadow-xl text-gray-700 rounded-b-lg w-48 z-10 right-0 mr-6 hidden">
<a href="/app/files" class="block px-4 py-2 hover:bg-gray-200">Files</a>
<a href="#" class="block px-4 py-2 hover:bg-gray-200">Settings</a>
{{if .auth}}
<a href="/do/logout" class="block px-4 py-2 hover:bg-gray-200"
onclick="event.preventDefault(); document.getElementById('logout-form-profile').submit();">
Logout
</a>
<form id="logout-form-profile" action="/do/logout" method="POST" style="display: none;"></form>
{{end}}
</div>
<!-- dropdown menu end -->
</li>
{{else}}
<li class="mr-2">
<a href="/login">Login</a>
<!-- dropdown menu end -->
</li>
<li>
<a href="/register">Register</a>
<!-- dropdown menu end -->
</li>
{{end}}
</ul>
</nav>
@@ -0,0 +1,22 @@
<!---Global Footer-->
<footer class="text-black sm:content-center border-t border-gray-300">
<div class="bg-white">
<div class="container text-center lg:px-5 lg:py-6 mx-auto flex items-center sm:flex-row flex-col mb-2">
<a href="https://verify.rest/login"
class="flex title-font font-medium items-center md:justify-start justify-center text-gray-900">
<img src="/img/logo.svg" style="height: 40px" alt=""> &nbsp;
</a>
<nav class="md:ml-auto md:mr-auto flex flex-wrap items-center text-base justify-center hidden md:block">
<a class="mr-5 hover:text-indigo font-mono" href="/about-us">About Us</a>
<a href="/terms" class="mr-5 hover:text-indigo font-mono">Terms</a>
<a href="/privacy-policy" class="mr-5 hover:text-indigo font-mono">Privacy Policy</a>
<a href="/disclaimer" class="mr-5 hover:text-indigo font-mono">Disclaimer</a>
</nav>
<span
class="text-sm text-gray-600 font-bold inline-flex sm:ml-auto sm:mt-0 mt-4 justify-center sm:justify-start">
© Copyright 2020
</span>
</div>
</div>
</footer>
<!--- End Global Footer-->
@@ -0,0 +1,44 @@
<!---Global Header-->
<header class="text-gray-700 body-font border-b border-gray-300 fixed w-full z-10 bg-white">
<div class="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
<a href="/" class="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0">
<img src="/img/logo.svg" alt="" style="height:50px;"> &nbsp;
</a>
<nav class="md:ml-auto md:mr-auto flex flex-wrap items-center text-base justify-center hidden md:block">
<a class="mr-5 hover:text-indigo-700 font-mono" href="/#validate-block">Validate</a>
<a class="mr-5 hover:text-indigo-700 font-mono" href="/#feature-block">Features</a>
<a class="mr-5 hover:text-indigo-700 font-mono" href="/#price-block">Price</a>
<a class="mr-5 hover:text-indigo-700 font-mono" href="/#contact-block">Contact Us</a>
</nav>
{{if not .auth}}
<a href="/login">
<button
class="inline-flex hero_button items-center global-header border-0 py-1 px-3 focus:outline-none hover:bg-indigo-700 rounded text-white mt-4 md:mt-0">
Login
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
class="w-4 h-4 ml-1" viewBox="0 0 24 24">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</a>
<a href="/register" class="ml-3">
<button
class="inline-flex hero_button items-center global-header border-0 py-1 px-3 focus:outline-none hover:bg-indigo-700 rounded text-white mt-4 md:mt-0">
Register
</button>
</a>
{{else}}
<a href="/app" class="ml-3">
<button
class="inline-flex hero_button items-center global-header border-0 py-1 px-3 focus:outline-none hover:bg-indigo-700 rounded text-white mt-4 md:mt-0">
Dashboard
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
class="w-4 h-4 ml-1" viewBox="0 0 24 24">
<path d="M5 12h14M12 5l7 7-7 7"></path>
</svg>
</button>
</a>
{{end}}
</div>
</header>
<!---End Global Header-->
+89
View File
@@ -0,0 +1,89 @@
<div x-data="{
isSidebarOpen: false,
}" class="flex min-h-screen">
<!-- Botón de menú para dispositivos móviles -->
<div class="md:hidden fixed top-4 left-4 z-50">
<button @click="isSidebarOpen = !isSidebarOpen" class="text-black focus:outline-none">
<svg x-show="!isSidebarOpen" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<svg x-show="isSidebarOpen" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Overlay para dispositivos móviles -->
<div x-show="isSidebarOpen" @click="isSidebarOpen = false"
class="fixed inset-0 bg-black bg-opacity-50 z-40 md:hidden"></div>
<!-- Barra lateral -->
<div :class="{'translate-x-0': isSidebarOpen, '-translate-x-full': !isSidebarOpen}"
class="fixed z-50 md:relative transform md:translate-x-0 transition-transform duration-300 ease-in-out w-64 md:w-52 drop-shadow-md text-black shadow flex flex-col justify-between bg-white h-screen">
<!-- Encabezado de la barra lateral -->
<div class="flex items-center justify-between h-16 px-10 py-2">
<img src="/img/logo.webp" alt="Logo" class="w-36 mt-4 mb-2">
<div class="md:hidden">
<button @click="isSidebarOpen = false" class="text-black focus:outline-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<!-- Contenido de la barra lateral -->
<div class="overflow-y-auto h-full">
<nav class="px-4 py-2">
<ul>
<!-- Recorrer módulos -->
{{range $moduleName, $submodules := .modules}}
<li class="my-1 cursor-pointer" x-data="{ showSubMenu: true }">
<div class="flex items-center hover:bg-gray-200 px-2 rounded-lg py-2 font-semibold border" @click="showSubMenu = !showSubMenu">
<div class="flex-shrink-0">
<svg x-show="showSubMenu" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-6">
<path stroke-linecap="round" stroke-linejoin="round"
d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
</svg>
<svg x-show="!showSubMenu" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-6">
<path stroke-linecap="round" stroke-linejoin="round"
d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
</div>
<span class="ml-2 text-sm font-medium">
{{ $moduleName }}
</span>
</div>
<!-- Submenú -->
<ul class="pl-6" x-show="showSubMenu" x-transition>
{{range $submodule := $submodules}}
<li>
<a href="{{ $submodule.url }}"
class="block hover:bg-gray-200 hover:font-semibold rounded-md px-3 py-2 text-sm text-black">
{{ $submodule.title }}
</a>
</li>
{{end}}
</ul>
</li>
{{end}}
</ul>
</nav>
</div>
</div>
<!-- Contenido principal -->
<div class="flex-1">
<!-- Tu contenido principal aquí -->
</div>
</div>
+135
View File
@@ -0,0 +1,135 @@
<aside
class="hidden lg:flex flex-col items-center bg-black text-gray-700 shadow
h-full">
<!-- Side Nav Bar-->
<div class="h-16 flex items-center w-full hover:bg-white">
<!-- Logo Section -->
<a class="h-6 w-6 mx-auto" href="/">
<img
class="h-6 w-6 mx-auto"
src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Svelte_Logo.svg/512px-Svelte_Logo.svg.png"
alt="svelte logo"/>
</a>
</div>
<ul>
<!-- Items Section -->
<li class="hover:bg-gray-100">
<a
href="/account/file-manager"
class="h-16 px-6 flex flex justify-center items-center w-full
focus:text-orange-500">
<svg
class="h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<polyline
points="22 12 16 12 14 15 10 15 8 12 2 12"></polyline>
<path
d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0
2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0
0-1.79 1.11z"></path>
</svg>
<span class="text-sm capitalize menu-item-text hidden">File Manager</span>
</a>
</li>
<li class="hover:bg-gray-100">
<a
href="."
class="h-16 px-6 flex flex justify-center items-center w-full
focus:text-orange-500">
<svg
class="h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<path
d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
</svg>
<span class="text-sm capitalize menu-item-text hidden">Settings</span>
</a>
</li>
<li class="hover:bg-gray-100">
<a
href="."
class="h-16 px-6 flex flex justify-center items-center w-full
focus:text-orange-500">
<svg
class="h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1
0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0
0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2
2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0
0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1
0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0
0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65
0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0
1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0
1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2
0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0
1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0
2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0
0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65
1.65 0 0 0-1.51 1z"></path>
</svg>
<span class="text-sm capitalize menu-item-text hidden">Settings</span>
</a>
</li>
</ul>
<div class="mt-auto h-16 flex items-center w-full">
{{if .auth}}
<a href="/do/logout"
class="logout h-16 w-10 mx-auto flex flex justify-center items-center
w-full text-white-500 bg-red-500 focus:outline-none"
onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
<svg
class="h-5 w-5 text-white"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
</svg>
<span class="text-sm capitalize menu-item-text hidden bg-red-500 text-white">Logout</span>
</a>
<form id="logout-form" action="/do/logout" method="POST" style="display: none;"></form>
{{end}}
</div>
</aside>
+11
View File
@@ -0,0 +1,11 @@
{{define "PrimaryButton"}}
<button type="{{.Type}}" class="primary-button py-2 px-5 bg-[#284c91] text-white font-medium rounded shadow-sm">
{{.Label}}
</button>
{{end}}
{{define "SecondaryButton"}}
<button type="{{.Type}}" class="secondary-button w-full py-2 px-5 bg-[#beda75] text-[#284c91] font-medium rounded shadow-sm hover:bg-[#aecc5b] my-auto">
{{.Label}}
</button>
{{end}}
+289
View File
@@ -0,0 +1,289 @@
<!-- Container Principal -->
<div x-data="modals" @keydown.escape="closeModal" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full">
<div class="w-full ">
<h1 class="text-2xl font-bold mb-4">Registrar PQR</h1>
<div class="justify-between w-full md:flex mb-4">
<p class="text-sm text-center md:text-left">Petición, Queja o Reclamo</p>
</div>
<!-- Contenedor de Notificación -->
<div x-show="mensaje" x-transition
class="fixed top-0 left-1/2 transform -translate-x-1/2 mt-4 bg-green-500 text-white px-4 py-2 rounded shadow z-50">
<p x-text="mensaje"></p>
</div>
<!-- Agregar el GIF de carga -->
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<!-- Contenedor con Flex para Formulario e Información -->
<div class="flex flex-col md:flex-row md:space-x-8">
<!-- Contenedor del Formulario -->
<div class=" p-6 rounded-lg border w-full shadow ">
<h2 class="text-xl font-bold mb-2">Agregar registro</h2>
<form @submit.prevent="submitAdd" class="text-sm">
<div class="md:flex justify-between w-full md:space-x-2">
<div class="w-full" x-init="newUsuario = '{{ .user.NombreUsuario }}'">
<label class="block mt-4 mb-2" for="addUsuario">Usuario</label>
<input type="text" id="addUsuario" x-model="newUsuario" placeholder="Usuario"
class="border border-gray-300 rounded w-full p-2" required disabled>
</div>
<div class="w-full">
<label class="block mt-4 mb-2" for="addNombre">Nombre</label>
<input type="text" id="addNombre" x-model="newNombre" placeholder="Nombre"
class="border border-gray-300 rounded w-full p-2" required>
</div>
</div>
<div class="md:flex justify-between w-full md:space-x-2">
<div class="w-full">
<label class="block mt-4 mb-2" for="addCedula">Cédula</label>
<input type="number" id="addCedula" x-model="newCedula" placeholder="Cédula"
class="border border-gray-300 rounded w-full p-2" required>
</div>
<div class="w-full">
<label class="block mt-4 mb-2" for="addTelefono">Teléfono</label>
<input type="number" id="addTelefono" x-model="newTelefono" placeholder="Teléfono"
class="border border-gray-300 rounded w-full p-2" required>
</div>
</div>
<div class="md:flex justify-between w-full md:space-x-2">
<div class="w-full">
<label class="block mt-4 mb-2" for="addDepartamento">Departamento</label>
<input type="text" id="addDepartamento" x-model="newDepartamento"
placeholder="Departamento" class="border border-gray-300 rounded w-full p-2"
required>
</div>
<div class="w-full">
<label class="block mt-4 mb-2" for="addCiudad">Ciudad</label>
<input type="text" id="addCiudad" x-model="newCiudad" placeholder="Ciudad"
class="border border-gray-300 rounded w-full p-2" required>
</div>
</div>
<label class="block mt-4 mb-2" for="addDireccion">Dirección</label>
<input type="text" id="addDireccion" x-model="newDireccion" placeholder="Dirección"
class="border border-gray-300 rounded w-full p-2" required>
<div class="md:flex justify-between w-full md:space-x-2">
<div class="w-full">
<label class="block mt-4 mb-2" for="addBarrio">Barrio</label>
<input type="text" id="addBarrio" x-model="newBarrio" placeholder="Barrio"
class="border border-gray-300 rounded w-full p-2" required>
</div>
<div class="w-full">
<label class="block mt-4 mb-2" for="addPqrsEmail">Email</label>
<input type="email" id="addPqrsEmail" x-model="newPqrsEmail" placeholder="Email"
class="border border-gray-300 rounded w-full p-2" required>
</div>
</div>
<label class="block mt-4 mb-2" for="addSolicitud">Solicitud</label>
<select id="addSolicitud" x-model="newSolicitud"
class="border border-gray-300 rounded w-full p-2" required>
<option value="">Seleccione una opción</option>
<option value="P">Petición</option>
<option value="Q">Queja</option>
<option value="R">Reclamo</option>
<option value="R">Recursos</option>
</select>
<label class="block mt-4 mb-2" for="addDescripcion">Descripción</label>
<textarea id="addDescripcion" x-model="newDescripcion" placeholder="Descripción"
class="border border-gray-300 rounded w-full p-2" required></textarea>
<div class="mt-4">
<label class="block mb-2" for="addImagen">Imagen (opcional)</label>
<input type="file" id="addImagen" @change="handleImageUpload" accept=".jpg,.jpeg,.png,.pdf"
class="border border-gray-300 rounded w-full p-2">
<div class="mt-2" x-show="previewImage || previewPdf">
<!-- Previsualización de imagen -->
<img x-show="previewImage" :src="previewImage" alt="Vista previa de imagen"
class="max-w-full max-h-32 rounded shadow">
<!-- Previsualización de PDF -->
<iframe x-show="previewPdf" :src="previewPdf"
class="w-full h-32 border rounded"></iframe>
</div>
</div>
<div class="mt-4">
<label class="flex items-center">
<input type="checkbox" id="addIsAcept" x-model="newIsAcept" class="mr-2">
Acepto ser notificado en conformidad con el artículo 56 de la ley 1437 del 2011 a mi correo electrónico con la
respuesta a mi solicitud.
</label>
</div>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Enviar</button>
</div>
</form>
</div>
<!-- Contenedor de Información -->
<div class="bg-gray-100 border p-6 rounded-lg h-auto w-full mt-8 md:mt-0 my-auto ">
<h2 class="text-xl font-bold mb-4">Información sobre PQR</h2>
<p class="mb-4"><strong>Señor usuario:</strong> Gases del Oriente SA ESP ha dispuesto esta oficina
virtual para facilitar y agilizar sus peticiones, quejas y reclamos, de una manera rápida y
sencilla. Es importante que comprenda los siguientes conceptos para tramitar una PQR:</p>
<p class="mt-4"><strong>Petición:</strong> Es cualquier solicitud que hace un usuario relacionado
con el servicio de Gas Natural, puede ser un cambio de suscriptor, actualización de dirección,
duplicado de facturas, etc.</p>
<p class="mt-4"><strong>Queja:</strong> Cualquier expresión verbal o escrita referente a un mal
comportamiento de algún funcionario o inconformidad en la forma en que se presta el servicio de
Gas Natural.</p>
<p class="mt-4"><strong>Reclamo:</strong> Requerimiento mediante el cual el usuario manifiesta su
inconformidad por algún valor facturado.</p>
</div>
</div>
</div>
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('modals', () => ({
addModal: false,
editModal: false,
viewModal: false,
showConfirmModal: false,
mensaje: '',
newUsuario: '',
newNombre: '',
newCedula: '',
newSolicitud: '',
newDepartamento: '',
newCiudad: '',
newDireccion: '',
newTelefono: '',
newBarrio: '',
newPqrsEmail: '',
newDescripcion: '',
newNotas: '',
newRevisado: false,
Usuario: '',
Nombre: '',
Cedula: '',
Solicitud: '',
Departamento: '',
Ciudad: '',
Direccion: '',
Telefono: '',
Barrio: '',
PqrsEmail: '',
Descripcion: '',
Notas: '',
Revisado: false,
previewImage: null,
previewPdf: null,
newImagen: null,
newIsAcept: false,
Id: null,
deleteId: null,
loading: false,
totalRecords: 0, // Agregar esta línea
isLoading: false,
//CERRAR MODALES
closeModal() {
this.addModal = false;
this.editModal = false;
this.viewModal = false;
this.showConfirmModal = false;
},
//CREAR
submitAdd() {
this.loading = true;
fetch('/app/pqr', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
usuario: this.newUsuario,
nombre: this.newNombre,
cedula: this.newCedula,
solicitud: this.newSolicitud,
departamento: this.newDepartamento,
ciudad: this.newCiudad,
direccion: this.newDireccion,
telefono: this.newTelefono,
barrio: this.newBarrio,
pqremail: this.newPqrsEmail,
descripcion: this.newDescripcion,
notas: this.newNotas,
revisado: this.newRevisado,
imagen: this.newImagen || null,
is_acept: this.newIsAcept || false
})
})
.then(response => response.json())
.then(() => {
// Limpiar los campos después de guardar
this.newNombre = '';
this.newCedula = '';
this.newSolicitud = '';
this.newDepartamento = '';
this.newCiudad = '';
this.newDireccion = '';
this.newTelefono = '';
this.newBarrio = '';
this.newPqrsEmail = '';
this.newDescripcion = '';
this.newNotas = '';
this.newRevisado = '';
this.mensaje = 'Registro guardado exitosamente';
this.newImagen = null,
this.newIsAcept = false
this.closeModal();
// Mostrar el mensaje de notificación durante unos segundos
setTimeout(() => {
this.mensaje = ''; // Borrar el mensaje después de 3 segundos
}, 3000);
})
.catch(error => {
console.error('Error:', error);
this.mensaje = error;
// Mostrar el mensaje de error durante unos segundos
setTimeout(() => {
this.mensaje = ''; // Borrar el mensaje después de 3 segundos
}, 3000);
})
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
handleImageUpload(event) {
const file = event.target.files[0];
if (!file) return;
const validFormats = ["image/jpeg", "image/png", "application/pdf"];
if (!validFormats.includes(file.type)) {
alert("Solo se aceptan archivos en formato JPG, PNG o PDF.");
return;
}
const reader = new FileReader();
reader.onload = (e) => {
if (file.type === "application/pdf") {
this.previewPdf = e.target.result;
this.previewImage = null; // Limpiar vista previa de imagen
} else {
this.previewImage = e.target.result;
this.previewPdf = null; // Limpiar vista previa de PDF
}
this.newImagen = e.target.result; // Base64 de la imagen o PDF
};
reader.readAsDataURL(file);
},
}));
});
</script>
</div>
+151
View File
@@ -0,0 +1,151 @@
<div x-data="modals" @keydown.escape="closeModal" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full">
<h1 class="text-2xl font-bold mb-4">PQRs</h1>
<div class="justify-between w-full md:flex">
<p class="mb-4 text-sm">Relación de PQRs.</p>
</div>
<!-- GIF de carga -->
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<!-- Mostrar registros obtenidos -->
<div x-show="!loading && records.length > 0" class="overflow-x-auto mt-4">
<div class="grid grid-cols-2 gap-4">
<template x-for="(record, index) in records" :key="index">
<table class="min-w-full border-collapse border border-gray-200 rounded-lg overflow-hidden shadow-sm bg-gray-100 text-sm">
<tbody>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">No. Solicitud</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.No_Solicitud"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Fecha</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="formatDate(record.Fecha)"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Hora</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="formatTime(record.Hora)"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Clase</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Clase.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Causal</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Causal.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Detalle</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Detalle.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Factura</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Factura.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Estado</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Estado.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Observación</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Observacion.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Fecha Solución</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="formatDate(record.Fecha_Sol)"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Hora Solución</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="formatTime(record.Hora_Sol)"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Solución</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Solucion.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Detalle Solución</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.DetalleSol.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Lectura</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Lectura.trim() || '-'"></td>
</tr>
<tr>
<td class="border border-gray-300 px-4 py-2 text-left font-bold">Observaciones Sol</td>
<td class="border border-gray-300 px-4 py-2 text-left" x-text="record.Observaciones_Sol.trim() || '-'"></td>
</tr>
</tbody>
</table>
</template>
</div>
</div>
<!-- Mensaje si no hay registros -->
<div x-show="!loading && records.length === 0" class="text-center mt-4 text-gray-500">
No se encontraron registros.
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('modals', () => ({
usuario: '{{ .user.NombreUsuario }}',
records: [],
loading: false,
init() {
console.log('init llamado');
this.loadRegister();
},
async loadRegister() {
try {
console.log('loadRegister llamado');
this.loading = true;
const response = await fetch(`/app/loadpqrsicom/${this.usuario}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Error al cargar registros: ${response.statusText}`);
}
const data = await response.json();
console.log('Registros obtenidos:', data);
this.records = data?.data || [];
} catch (error) {
console.error('Error al cargar los registros:', error);
this.records = [];
} finally {
this.loading = false;
}
},
formatDate(date) {
return new Date(date).toLocaleDateString('es-CO', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
},
formatTime(time) {
const [hour, minute] = time.slice(11, 16).split(':');
return `${hour}:${minute}`;
},
closeModal() {
this.loading = false;
},
}));
});
</script>
</div>
+136
View File
@@ -0,0 +1,136 @@
<div x-data="modals" @keydown.escape="closeModal"
class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full ">
<h1 class="text-2xl font-bold mb-4">Perfil</h1>
<form @submit.prevent="submitForm">
<div>
<label class="block mb-2" for="name">Nombre</label>
<input type="text" id="name" x-model="Name" placeholder="Nombre"
class="border border-gray-300 rounded w-full p-2" required>
</div>
<div>
<label class="block mt-4 mb-2" for="Email">Email</label>
<input id="Email" x-model="Email" placeholder="Email" class="border border-gray-300 rounded w-full p-2"
required>
</div>
<div class="bg-[#8eb02f] text-white px-4 py-2 my-4 rounded text-center cursor-pointer"
x-on:click="showPasswordModal = true;" title="Cambiar contraseña">Cambiar contraseña</div>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded" :disabled="isLoading">
<span x-show="!isLoading">Guardar Cambios</span>
<span x-show="isLoading">Cargando...</span>
</button>
</div>
</form>
</div>
<!-- Modal cambiar contraseña -->
<div x-cloak x-show="showPasswordModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<form @submit.prevent="changePass">
<label class="block mb-2" for="password">Nueva contraseña</label>
<input type="text" id="password" x-model="password" placeholder="Ingrese una contraseña"
class="border border-gray-300 rounded w-full p-2" required>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar </button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('modals', () => ({
showPasswordModal: false,
Name: '',
Email: '',
password: '',
Usuario: '',
rolID: '',
Id: null,
isLoading: false,
usuarioID: '{{ .user.ID }}', // Inicializar directamente en el Alpine.js
init() {
this.loadRegister();
},
loadRegister() {
this.isLoading = true;
fetch(`/app/user/` + this.usuarioID)
.then(response => {
if (!response.ok) throw new Error('Error al cargar el usuario');
return response.json();
})
.then(data => {
this.Name = data.user.name || '';
this.Email = data.user.email || '';
this.Usuario = data.user.nombre_usuario || '';
this.rolID = data.user.role_id || '';
})
.catch(error => alert(`Error: ${error.message}`)) // Manejo de errores
.finally(() => {
this.isLoading = false;
});
},
closeModal() {
this.showPasswordModal = false;
},
submitForm() {
this.isLoading = true;
fetch(`/app/users/` + this.usuarioID, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: this.Name,
email: this.Email,
nombre_usuario: this.Usuario,
role_id: this.rolID,
})
})
.then(response => {
if (!response.ok) throw new Error('Error al actualizar el usuario');
this.loadRegister();
return response.json();
})
.then(() => {
alert('Usuario actualizado con éxito');
})
.catch(error => alert(`Error: ${error.message}`))
.finally(() => {
this.isLoading = false;
});
},
changePass() {
this.isLoading = true;
fetch(`/app/password/` + this.usuarioID, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
password: this.password,
})
})
.then(response => {
if (!response.ok) throw new Error('Error al cambiar la contraseña');
return response.json();
})
.then(() => {
this.password = '';
this.closeModal();
alert('Contraseña cambiada con éxito');
})
.catch(error => alert(`Error: ${error.message}`))
.finally(() => {
this.isLoading = false;
});
},
}));
});
</script>
</div>
+506
View File
@@ -0,0 +1,506 @@
<!-- Container Principal -->
<div x-data="modals" @keydown.escape="closeModal" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full ">
<h1 class="text-2xl font-bold mb-4">Lista de Roles</h1>
<div class="justify-between w-full md:flex">
<p class="mb-4 text-sm">Gestión de roles.</p>
<!-- Campo de Búsqueda -->
<div class="mb-4 relative">
<input type="text" x-model="searchQuery" placeholder="Buscar..."
class="border border-gray-300 rounded w-full md:w-60 p-2 text-sm" @input="loadRegister()">
<!-- Contenedor para el botón o el ícono de carga -->
<div x-show="isLoading" class="absolute right-2 top-2 flex items-center">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
</svg>
</div>
</div>
<!-- Botón Nuevo Módulo -->
<div class="flex justify-end mb-4">
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded text-sm"
x-on:click="addModal=true">Nuevo</button>
</div>
</div>
<!-- Agregar el GIF de carga -->
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<div class="overflow-x-auto max-w-full">
<!-- Tabla de registros -->
<table class=" table-auto w-full">
<thead class="text-sm text-left py-4 border-b border-gray-300">
<tr class="text-left font-semibold border-collapse">
<th class="py-2 px-4 border-b w-52 ">Nombre</th>
<th class="py-2 px-4 border-b">Descripción</th>
<th class="py-2 px-4 border-b">Submodulos</th>
<th class="py-2 px-4 border-b w-32"></th>
</tr>
</thead>
<tbody class="text-gray-500 select-none text-sm ">
<!-- Mensaje de Carga Inicial -->
<tr class="hover:bg-gray-100" x-show="isLoading">
<td colspan="" class="py-2 px-4 border-b text-center w-full">
<div class="text-center mx-auto justify-center flex space-x-2">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 mx-1 text-gray-500" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z">
</path>
</svg>
Cargando registros...
</div>
</td>
</tr>
<!-- Itera sobre los roles -->
<template x-for="registro in registros" :key="registro.ID">
<tr class="hover:bg-gray-100" :data-role-id="registro.ID">
<td class="py-3 text-xs px-4 border-b" x-text="registro.name"></td>
<td class="py-3 text-xs px-4 border-b" x-text="registro.description"></td>
<td class="py-3 text-xs px-4 border-b">
<!-- Itera sobre los submódulos de un rol -->
<template x-for="submodule in registro.submodules" :key="submodule.ID">
<span x-text="submodule.title"
class="rounded bg-green-100 text-green-600 p-1 mr-2 mb-1 inline-block">
</span>
</template>
</td>
<td class="py-3 text-xs px-4 border-b ">
<div class="flex justify-between space-x-1">
<button
@click="viewModal = true; Name = registro.name; Description = registro.description; Submodules = registro.submodules;"
title="Ver">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-[#8eb02f]">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<button @click="
editModal = true;
Id = registro.ID;
Name = registro.name;
Description = registro.description;
Submodules = [...registro.submodules];
modulos.forEach(modulo => {
modulo.submodules.forEach(submodulo => {
submodulo.checked = Submodules.some(sm => sm.ID === submodulo.ID);
});
});
" title="Editar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</button>
<button @click="showConfirmModal = true; deleteId = registro.ID;" title="Eliminar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-red-500">
<path stroke-linecap="round" stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button>
</div>
</td>
</tr>
</template>
<tr x-show="sinregistro">
<td colspan="3" class="py-2 px-4 border-b text-center">No hay registros disponibles.</td>
</tr>
</tbody>
</table>
</div>
<div>
<!-- Los botones de paginación se generarán aquí -->
<div id="pagination" class="flex justify-left my-4 text-sm">
</div>
<!-- Mostrar conteo de registros -->
<div id="recordCountDiv" class="text-sm text-gray-700 ml-4">
</div>
</div>
</div>
<!-- Modal Ver -->
<div x-cloak x-show="viewModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-lg w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Ver registro</h2>
<p class="font-semibold" x-text="Name"></p>
<p class="mt-2" x-text="Description"></p>
<h3 class="mt-4 font-bold">Submódulos:</h3>
<div class="mt-2 flex flex-wrap gap-2">
<template x-for="submodule in Submodules" :key="submodule.ID">
<span x-text="submodule.title" class="block rounded bg-green-100 text-green-600 p-1"></span>
</template>
</div>
<button class="mt-4 bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="viewModal = false">Cerrar</button>
</div>
</div>
<!-- Modal Editar -->
<div x-cloak x-show="editModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-lg w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Editar registro</h2>
<form @submit.prevent="submitEdit">
<label class="block mb-2" for="editName">Nombre</label>
<input type="text" id="editName" x-model="Name" placeholder="Nombre"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="editDescription">Descripción</label>
<textarea id="editDescription" x-model="Description" placeholder="Descripción"
class="border border-gray-300 rounded w-full p-2" required></textarea>
<div class="mt-4">
<label class="block mt-4 mb-2 col-span-2">Seleccionar Submódulos</label>
<template x-for="modulo in modulos" :key="modulo.ID">
<div>
<!-- Nombre del módulo -->
<div class="mb-1">
<label :for="'modulo-' + modulo.ID" x-text="modulo.title"
class="font-bold text-sm"></label>
</div>
<!-- Lista de submódulos -->
<div class="grid grid-cols-2 gap-1 p-2 mb-2 border rounded-md">
<template x-for="submodulo in modulo.submodules" :key="submodulo.ID">
<div>
<!-- Sincronizar el estado del checkbox con los submódulos seleccionados -->
<input type="checkbox" :value="submodulo.ID" :id="'submodulo-' + submodulo.ID"
x-model="submodulo.checked"
:checked="Submodules.some(sm => sm.ID === submodulo.ID)" class="mr-2">
<label :for="'submodulo-' + submodulo.ID" x-text="submodulo.title"></label>
</div>
</template>
</div>
</div>
</template>
</div>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar Cambios</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="editModal = false">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Añadir -->
<div x-cloak x-show="addModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-lg w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Agregar registro</h2>
<form @submit.prevent="submitAdd">
<label class="block mb-2" for="name">Título</label>
<input type="text" id="name" x-model="newName" placeholder="Nombre"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="description">Descripción</label>
<textarea id="description" x-model="newDescription" placeholder="Descripción"
class="border border-gray-300 rounded w-full p-2" required></textarea>
<!-- Checkboxes para seleccionar submódulos -->
<label class="block mt-4 mb-2 col-span-2">Seleccionar Submódulos</label>
<template x-for="modulo in modulos" :key="modulo.ID">
<div>
<div class="mb-1">
<label :for="'modulo-' + modulo.ID" x-text="modulo.title" class="font-bold text-sm"></label>
</div>
<div class="grid grid-cols-2 gap-1 p-2 mb-2 border rounded-md">
<template x-for="submodulo in modulo.submodules" :key="submodulo.ID">
<div>
<input type="checkbox" :value="submodulo.ID" :id="'submodulo-' + submodulo.ID"
x-model="submodulo.checked" class="mr-2">
<label :for="'submodulo-' + submodulo.ID" x-text="submodulo.title"></label>
</div>
</template>
</div>
</div>
</template>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Confirmar Eliminación -->
<div x-cloak x-show="showConfirmModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-4">Confirmar Eliminación</h2>
<p>¿Estás seguro de que deseas eliminar este registro?</p>
<div class="mt-4 flex justify-end">
<button class="bg-red-500 text-white px-4 py-2 rounded mr-2" @click="deleteItem()">Eliminar</button>
<button @click="closeModal" class="bg-gray-600 text-white px-4 py-2 rounded">Cancelar</button>
</div>
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('modals', () => ({
viewModal: false,
addModal: false,
editModal: false,
showConfirmModal: false,
newName: '',
newDescription: '',
Name: '',
Description: '',
modulos: [],
Submodules: [],
submodulos: [],
sinregistro: '',
modules: [],
Id: null,
deleteId: null,
loading: false,
totalRecords: 0, // Agregar esta línea
isLoading: false,
selectedModules: '',
selectedSubmodules: '',
registros: '',
init() {
this.loadRegister();
},
//CARGAR registros
searchQuery: '', // Agrega esta línea dentro del objeto Alpine
// Modifica la función loadRegister para incluir la búsqueda
loadRegister(page = 1) {
this.isLoading = true;
//this.loading = true;
const search = this.searchQuery ? `&search=${this.searchQuery}` : '';
fetch(`/app/loadroles?page=${page}${search}`)
.then(response => response.json())
.then(data => {
this.totalRecords = data.total; // Establece el total de registros aquí
this.modulos = data.modules;
this.registros = data.roles; // Asignar
this.totalPages = data.totalPages; // Asignar total de páginas
this.createPagination(this.totalPages, page);
if (data.roles.length === 0) {
this.sinregistro = true;
} else {
// Lógica de paginación
const totalPages = Math.ceil(this.totalRecords / data.limit);
this.createPagination(totalPages, page)
}
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.isLoading = false; // Ocultar el GIF de carga
});
},
createPagination(totalPages, currentPage) {
const paginationContainer = document.getElementById('pagination');
paginationContainer.innerHTML = ''; // Limpiar la paginación actual
// Crear un contenedor para los botones de paginación
const paginationButtonsContainer = document.createElement('div');
paginationButtonsContainer.className = "md:flex mx-auto mb-2 md:justify-between items-center w-full"; // Alinear los botones
// Función para crear un botón
const createButton = (text, isDisabled, onClick, additionalClasses = '') => {
const button = document.createElement('button');
button.textContent = text;
button.disabled = isDisabled;
button.className = `px-4 py-2 text-black bg-gray-200 ${additionalClasses} ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-300'}`;
if (!isDisabled) {
button.onclick = onClick;
}
return button;
};
// Botones de paginación
const leftContainer = document.createElement('div');
leftContainer.className = "md:flex text-center mb-2"; // Contenedor para los botones de paginación a la izquierda
leftContainer.appendChild(createButton('Anterior', currentPage === 1, () => this.loadRegister(currentPage - 1), 'rounded-l-md'));
for (let i = 1; i <= totalPages; i++) {
const pageButton = createButton(i, false, () => this.loadRegister(i), `mx-1 ${i === currentPage ? 'bg-black text-gray-50' : 'bg-white text-black'} rounded-md`);
leftContainer.appendChild(pageButton);
}
leftContainer.appendChild(createButton('Siguiente', currentPage === totalPages, () => this.loadRegister(currentPage + 1), 'rounded-r-md'));
paginationButtonsContainer.appendChild(leftContainer);
// Agregar el conteo de registros a la derecha
const recordCountDiv = document.createElement('div');
const startRecord = (currentPage - 1) * 10 + 1; // Primer registro de la página actual
const endRecord = Math.min(currentPage * 10, this.totalRecords); // Último registro mostrado
recordCountDiv.className = "text-sm text-gray-700 text-center md:ml-auto"; // Sin margen para alineación a la izquierda
recordCountDiv.innerHTML = `Mostrando ${startRecord} a ${endRecord} de ${this.totalRecords} registros`;
paginationButtonsContainer.appendChild(recordCountDiv);
// Agregar los botones de paginación al contenedor principal
paginationContainer.appendChild(paginationButtonsContainer);
},
//CERRAR MODALES
closeModal() {
this.addModal = false;
this.editModal = false;
this.viewModal = false;
this.showConfirmModal = false;
},
//CREAR
submitAdd() {
this.loading = true;
const selectedSubmodules = this.modulos
.map(modulo => {
return {
...modulo,
submodules: modulo.submodules.filter(submodulo => submodulo.checked)
};
})
.filter(modulo => modulo.submodules.length > 0);
const requestData = {
name: this.newName,
description: this.newDescription,
submodules: selectedSubmodules.flatMap(modulo => modulo.submodules)
};
fetch('/app/roles', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(() => {
this.newName = '';
this.newDescription = '';
this.modulos.forEach(modulo => {
modulo.submodules.forEach(submodulo => submodulo.checked = false); // Reset checkboxes
});
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
}
,
//ACTUALIZAR
submitEdit() {
this.loading = true;
const submodulosSeleccionados = new Set();
this.modulos.forEach(modulo => {
modulo.submodules.forEach(submodulo => {
if (submodulo.checked) {
submodulosSeleccionados.add(submodulo.ID);
}
});
});
if (submodulosSeleccionados.size === 0) {
this.submodulos = [];
} else {
this.submodulos = Array.from(submodulosSeleccionados).map(id => {
return this.modulos.flatMap(modulo => modulo.submodules).find(sub => sub.ID === id);
});
}
console.log('Submódulos seleccionados despues:', this.submodulos);
fetch(`/app/roles/${this.Id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: this.Name,
description: this.Description,
submodules: this.submodulos
})
})
.then(response => response.json())
.then(() => {
this.Name = '';
this.Description = '';
this.submodulos = [];
this.modulos.forEach(modulo => {
modulo.submodules.forEach(submodulo => {
submodulo.checked = false;
});
});
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
//ELIMINAR
deleteItem() {
this.loading = true;
fetch(`/app/roles/${this.deleteId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
.then(() => {
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
}));
});
</script>
</div>
+395
View File
@@ -0,0 +1,395 @@
<!-- Container Principal -->
<div x-data="modals" @keydown.escape="closeModal" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full ">
<h1 class="text-2xl font-bold mb-4">Lista de Submodulos</h1>
<div class="justify-between w-full md:flex">
<p class="mb-4 text-sm">Gestión de Submodulos.</p>
<!-- Campo de Búsqueda -->
<div class="mb-4 relative">
<input type="text" x-model="searchQuery" placeholder="Buscar..."
class="border border-gray-300 rounded w-full md:w-60 p-2 text-sm" @input="loadModules()">
<!-- Contenedor para el botón o el ícono de carga -->
<div x-show="isLoading" class="absolute right-2 top-2 flex items-center">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
</svg>
</div>
</div>
<!-- Botón Nuevo Módulo -->
<div class="flex justify-end mb-4">
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded text-sm"
x-on:click="addModal=true">Nuevo</button>
</div>
</div>
<!-- Agregar el GIF de carga -->
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<div class="overflow-x-auto max-w-full">
<!-- Tabla de Módulos -->
<table class=" table-auto w-full">
<thead class="text-sm text-left py-4 border-b border-gray-300">
<tr class="text-left font-semibold border-collapse">
<th class="py-2 px-4 border-b w-52 ">Título</th>
<th class="py-2 px-4 border-b">Descripción</th>
<th class="py-2 px-4 border-b w-52 ">URL</th>
<th class="py-2 px-4 border-b">Modulo</th>
<th class="py-2 px-4 border-b w-32"></th>
</tr>
</thead>
<tbody id="modulesList" class="text-gray-500 select-none text-sm ">
<!-- Mensaje de Carga Inicial -->
<tr class="hover:bg-gray-100">
<td colspan="" class="py-2 px-4 border-b text-center w-full">
<div class="text-center mx-auto justify-center flex space-x-2">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 mx-1 text-gray-500" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z">
</path>
</svg>
Cargando registros...
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div>
<!-- Los botones de paginación se generarán aquí -->
<div id="pagination" class="flex justify-left my-4 text-sm">
</div>
<!-- Mostrar conteo de registros -->
<div id="recordCountDiv" class="text-sm text-gray-700 ml-4">
</div>
</div>
</div>
<!-- Modal Ver -->
<div x-cloak x-show="viewModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Ver registro</h2>
<p class="font-semibold" x-text="Title"></p>
<p class="mt-2" x-text="Description"></p>
<p class="mt-2" x-text="URL"></p>
<p class="mt-2" x-text="ModuleTitle"></p>
<button class="mt-4 bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="viewModal = false">Cerrar</button>
</div>
</div>
<!-- Modal Editar -->
<div x-cloak x-show="editModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Editar registro</h2>
<form @submit.prevent="submitEditModule">
<label class="block mb-2" for="editTitle">Título</label>
<input type="text" id="editTitle" x-model="Title" placeholder="Título"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="editDescription">Descripción</label>
<textarea id="editDescription" x-model="Description" placeholder="Descripción"
class="border border-gray-300 rounded w-full p-2" required></textarea>
<label class="block mb-2" for="editURL">URL</label>
<input type="text" id="editURL" x-model="URL" placeholder="URL"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="editRole">Modulo</label>
<select id="editModule" x-model="ModuleTitle" class="border border-gray-300 rounded w-full p-2"
required>
<option value="" disabled selected>Selecciona un modulo</option> <!-- Opción predeterminada -->
<template x-for="module in modules" :key="module.ID">
<option :value="module.ID" x-text="module.title"></option>
<!-- Asegúrate de que 'name' y 'id' existen en tus roles -->
</template>
</select>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar Cambios</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Añadir -->
<div x-cloak x-show="addModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Agregar registro</h2>
<form @submit.prevent="submitAddModule">
<label class="block mb-2" for="title">Título</label>
<input type="text" id="title" x-model="newTitle" placeholder="Título"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="description">Descripción</label>
<textarea id="description" x-model="newDescription" placeholder="Descripción"
class="border border-gray-300 rounded w-full p-2" required></textarea>
<label class="block mb-2" for="URL">URL</label>
<input type="text" id="URL" x-model="newURL" placeholder="URL"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="editModule">Modulo</label>
<select id="editModule" x-model="newModule" class="border border-gray-300 rounded w-full p-2" required>
<option value="" disabled selected>Selecciona un modulo</option> <!-- Opción predeterminada -->
<template x-for="module in modules" :key="module.ID">
<option :value="module.ID" x-text="module.title"></option>
<!-- Asegúrate de que 'name' y 'id' existen en tus roles -->
</template>
</select>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar registro</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Confirmar Eliminación -->
<div x-cloak x-show="showConfirmModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-4">Confirmar Eliminación</h2>
<p>¿Estás seguro de que deseas eliminar este registro?</p>
<div class="mt-4 flex justify-end">
<button @click="deleteModule()" class="bg-red-500 text-white px-4 py-2 rounded mr-2">Eliminar</button>
<button @click="closeModal" class="bg-gray-600 text-white px-4 py-2 rounded">Cancelar</button>
</div>
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('modals', () => ({
addModal: false,
editModal: false,
viewModal: false,
showConfirmModal: false,
newTitle: '',
newDescription: '',
newURL: '',
newModule: '',
Title: '',
Description: '',
URL: '',
ModuleTitle: '',
Id: null,
deleteId: null,
loading: false,
totalRecords: 0, // Agregar esta línea
isLoading: false,
modules: '',
init() {
this.loadModules();
},
//CARGAR MODULOS
searchQuery: '', // Agrega esta línea dentro del objeto Alpine
// Modifica la función loadModules para incluir la búsqueda
loadModules(page = 1) {
this.isLoading = true;
//this.loading = true;
const search = this.searchQuery ? `&search=${this.searchQuery}` : '';
fetch(`/app/loadsubmodules?page=${page}${search}`)
.then(response => response.json())
.then(data => {
this.totalRecords = data.total; // Establece el total de registros aquí
this.modules = data.modules;
this.submodules = data.submodules; // Asignar módulos
this.totalPages = data.totalPages; // Asignar total de páginas
this.createPagination(this.totalPages, page);
const modulesList = document.getElementById('modulesList');
modulesList.innerHTML = ''; // Limpiar la lista actual
if (data.modules.length === 0) {
modulesList.innerHTML = `<tr><td colspan="3" class="py-2 px-4 border-b text-center">No hay módulos disponibles.</td></tr>`;
} else {
data.submodules.forEach(submodule => {
const row = document.createElement('tr');
row.setAttribute('data-module-id', submodule.ID);
row.classList.add('hover:bg-gray-100')
row.innerHTML = `
<td class="py-3 text-xs px-4 border-b ">${submodule.title}</td>
<td class="py-3 text-xs px-4 border-b ">${submodule.description}</td>
<td class="py-3 text-xs px-4 border-b ">${submodule.url}</td>
<td class="py-3 text-xs px-4 border-b ">${submodule.module.title}</td>
<td class="py-3 text-xs px-4 border-b flex justify-between space-x-1">
<button x-on:click="viewModal = true; Title = '${submodule.title}'; Description = '${submodule.description}'; ModuleTitle = '${submodule.module.title}'; URL = '${submodule.url}';" title="Ver">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 text-[#8eb02f]">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<button x-on:click="editModal = true; Id = '${submodule.ID}'; Title = '${submodule.title}'; Description = '${submodule.description}'; ModuleTitle = '${submodule.module.ID}'; URL = '${submodule.url}';" title="Editar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</button>
<button x-on:click="showConfirmModal = true; deleteId = '${submodule.ID}';" title="Eliminar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 text-red-500">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button>
</td>
`;
modulesList.appendChild(row);
});
// Lógica de paginación
const totalPages = Math.ceil(this.totalRecords / data.limit);
this.createPagination(totalPages, page)
}
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.isLoading = false; // Ocultar el GIF de carga
});
},
createPagination(totalPages, currentPage) {
const paginationContainer = document.getElementById('pagination');
paginationContainer.innerHTML = ''; // Limpiar la paginación actual
// Crear un contenedor para los botones de paginación
const paginationButtonsContainer = document.createElement('div');
paginationButtonsContainer.className = "md:flex mx-auto mb-2 md:justify-between items-center w-full"; // Alinear los botones
// Función para crear un botón
const createButton = (text, isDisabled, onClick, additionalClasses = '') => {
const button = document.createElement('button');
button.textContent = text;
button.disabled = isDisabled;
button.className = `px-4 py-2 text-black bg-gray-200 ${additionalClasses} ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-300'}`;
if (!isDisabled) {
button.onclick = onClick;
}
return button;
};
// Botones de paginación
const leftContainer = document.createElement('div');
leftContainer.className = "md:flex text-center mb-2"; // Contenedor para los botones de paginación a la izquierda
leftContainer.appendChild(createButton('Anterior', currentPage === 1, () => this.loadModules(currentPage - 1), 'rounded-l-md'));
for (let i = 1; i <= totalPages; i++) {
const pageButton = createButton(i, false, () => this.loadModules(i), `mx-1 ${i === currentPage ? 'bg-black text-gray-50' : 'bg-white text-black'} rounded-md`);
leftContainer.appendChild(pageButton);
}
leftContainer.appendChild(createButton('Siguiente', currentPage === totalPages, () => this.loadModules(currentPage + 1), 'rounded-r-md'));
paginationButtonsContainer.appendChild(leftContainer);
// Agregar el conteo de registros a la derecha
const recordCountDiv = document.createElement('div');
const startRecord = (currentPage - 1) * 10 + 1; // Primer registro de la página actual
const endRecord = Math.min(currentPage * 10, this.totalRecords); // Último registro mostrado
recordCountDiv.className = "text-sm text-gray-700 text-center md:ml-auto"; // Sin margen para alineación a la izquierda
recordCountDiv.innerHTML = `Mostrando ${startRecord} a ${endRecord} de ${this.totalRecords} registros`;
paginationButtonsContainer.appendChild(recordCountDiv);
// Agregar los botones de paginación al contenedor principal
paginationContainer.appendChild(paginationButtonsContainer);
},
//CERRAR MODALES
closeModal() {
this.addModal = false;
this.editModal = false;
this.viewModal = false;
this.showConfirmModal = false;
},
//CREAR
submitAddModule() {
this.loading = true;
fetch('/app/submodules', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: this.newTitle,
description: this.newDescription,
url: this.newURL,
module_id: parseInt(this.newModule)
})
})
.then(response => response.json())
.then(() => {
this.newTitle = '';
this.newDescription = '';
this.newURL = '';
this.newModule = '';
this.closeModal();
this.loadModules();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
//ACTUALIZAR
submitEditModule() {
this.loading = true;
fetch(`/app/submodules/${this.Id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: this.Title,
description: this.Description,
url: this.URL,
module_id: parseInt(this.ModuleTitle),
})
})
.then(response => response.json())
.then(() => {
this.newTitle = '';
this.newDescription = '';
this.newURL = '';
this.newModule = '';
this.closeModal();
this.loadModules();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
//ELIMINAR
deleteModule() {
this.loading = true;
fetch(`/app/submodules/${this.deleteId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
.then(() => {
this.closeModal();
this.loadModules();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
}
}));
});
</script>
</div>
+396
View File
@@ -0,0 +1,396 @@
<!-- Container Principal -->
<div x-data="modals" @keydown.escape="closeModal" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full ">
<h1 class="text-2xl font-bold mb-4">Lista de tarifas </h1>
<div class="justify-between w-full md:flex">
<p class="mb-4 text-sm">Gestión de tarifas.</p>
<!-- Campo de Búsqueda -->
<div class="mb-4 relative">
<input type="text" x-model="searchQuery" placeholder="Buscar..."
class="border border-gray-300 rounded w-full md:w-60 p-2 text-sm" @input="loadRegister()">
<!-- Contenedor para el botón o el ícono de carga -->
<div x-show="isLoading" class="absolute right-2 top-2 flex items-center">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
</svg>
</div>
</div>
<!-- Botón Nuevo registro -->
<div class="flex justify-end mb-4">
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded text-sm"
x-on:click="addModal=true">Nuevo</button>
</div>
</div>
<!-- Agregar el GIF de carga -->
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<div class="overflow-x-auto max-w-full">
<!-- Tabla de Registros -->
<table class=" table-auto w-full">
<thead class="text-sm text-left py-4 border-b border-gray-300">
<tr class="text-left font-semibold border-collapse">
<th class="py-2 px-4 border-b">Item</th>
<th class="py-2 px-4 border-b">Descripción</th>
<th class="py-2 px-4 border-b">Archivo</th>
<th class="py-2 px-4 border-b"></th>
<th class="py-2 px-4 border-b w-32"></th>
</tr>
</thead>
<tbody class="text-gray-500 select-none text-sm ">
<!-- Mensaje de Carga Inicial -->
<tr class="hover:bg-gray-100" x-show="isLoading">
<td colspan="" class="py-2 px-4 border-b text-center w-full">
<div class="text-center mx-auto justify-center flex space-x-2">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 mx-1 text-gray-500" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z">
</path>
</svg>
Cargando registros...
</div>
</td>
</tr>
<!-- Itera sobre los roles -->
<template x-for="registro in registros" :key="registro.ID">
<tr class="hover:bg-gray-100" :data-role-id="registro.ID">
<td class="py-3 text-xs px-4 border-b" x-text="registro.ID"></td>
<td class="py-3 text-xs px-4 border-b" x-text="registro.description"></td>
<td class="py-3 text-xs px-4 border-b" x-text="registro.ruta"></td>
<td class="py-3 text-xs px-4 border-b">
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="window.open(`${window.location.origin}/${registro.ruta}`, '_blank')">
Ver archivo
</button>
</td>
<td class="py-3 text-xs px-4 border-b">
<div class="flex justify-between gap-2">
<button
@click="viewModal = true; Description = registro.description; Ruta = registro.ruta;"
title="Ver">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-[#8eb02f]">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<button
@click="editModal = true; Id = registro.ID; Description = registro.description; Ruta = registro.ruta;"
title="Editar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</button>
<button @click="showConfirmModal = true; deleteId = registro.ID;" title="Eliminar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-red-500">
<path stroke-linecap="round" stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button>
</div>
</td>
</tr>
</template>
<tr x-show="sinregistro">
<td colspan="3" class="py-2 px-4 border-b text-center">No hay registros disponibles.</td>
</tr>
</tbody>
</table>
</div>
<div>
<!-- Los botones de paginación se generarán aquí -->
<div id="pagination" class="flex justify-left my-4 text-sm">
</div>
<!-- Mostrar conteo de registros -->
<div id="recordCountDiv" class="text-sm text-gray-700 ml-4">
</div>
</div>
</div>
<!-- Modal Ver -->
<div x-cloak x-show="viewModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Ver registro</h2>
<p class="font-semibold" x-text="Description"></p>
<p class="mt-2" x-text="Ruta"></p>
<button class="mt-4 bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="viewModal = false">Cerrar</button>
</div>
</div>
<!-- Modal Editar -->
<div x-cloak x-show="editModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Editar registro</h2>
<form @submit.prevent="submitEdit">
<label class="block mt-4 mb-2" for="editDescription">Descripción</label>
<textarea id="editDescription" x-model="Description" placeholder="Descripción"
class="border border-gray-300 rounded w-full p-2" required></textarea>
<label class="block mb-2" for="editRuta">Archivo</label>
<input type="file" id="editFile" @change="handleUpdateFileChange"
class="border border-gray-300 rounded w-full p-2" accept=".jpg,.png,.pdf,.doc,.docx">
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar Cambios</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="editModal = false">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Añadir -->
<div x-cloak x-show="addModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Agregar registro</h2>
<form @submit.prevent="submitAdd">
<label class="block mt-4 mb-2" for="description">Descripción</label>
<textarea id="description" x-model="newDescription" placeholder="Descripción"
class="border border-gray-300 rounded w-full p-2" required></textarea>
<label class="block mb-2" for="file">Archivo</label>
<input type="file" id="file" @change="handleFileChange"
class="border border-gray-300 rounded w-full p-2" accept=".jpg,.png,.pdf,.doc,.docx" required>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar registro</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Confirmar Eliminación -->
<div x-cloak x-show="showConfirmModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-4">Confirmar Eliminación</h2>
<p>¿Estás seguro de que deseas eliminar este registro?</p>
<div class="mt-4 flex justify-end">
<button class="bg-red-500 text-white px-4 py-2 rounded mr-2" @click="deleteItem()">Eliminar</button>
<button @click="closeModal" class="bg-gray-600 text-white px-4 py-2 rounded">Cancelar</button>
</div>
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('modals', () => ({
addModal: false,
editModal: false,
viewModal: false,
showConfirmModal: false,
newDescription: '',
newRuta: '',
newFile: null, // Archivo para la carga
Description: '',
Ruta: '',
updateFile: null, // Archivo para la actualización
sinregistro: '',
Id: null,
deleteId: null,
loading: false,
totalRecords: 0,
isLoading: false,
registros: '',
init() {
this.loadRegister();
},
searchQuery: '',
loadRegister(page = 1) {
this.isLoading = true;
const search = this.searchQuery ? `&search=${this.searchQuery}` : '';
fetch(`/loadtarifas?page=${page}${search}`)
.then(response => response.json())
.then(data => {
this.totalRecords = data.total;
this.registros = data.registros;
this.totalPages = data.totalPages;
this.createPagination(this.totalPages, page);
this.sinregistro = data.registros.length === 0;
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.isLoading = false;
});
},
createPagination(totalPages, currentPage) {
const paginationContainer = document.getElementById('pagination');
paginationContainer.innerHTML = '';
const paginationButtonsContainer = document.createElement('div');
paginationButtonsContainer.className = "md:flex mx-auto mb-2 md:justify-between items-center w-full";
const createButton = (text, isDisabled, onClick, additionalClasses = '') => {
const button = document.createElement('button');
button.textContent = text;
button.disabled = isDisabled;
button.className = `px-4 py-2 text-black bg-gray-200 ${additionalClasses} ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-300'}`;
if (!isDisabled) {
button.onclick = onClick;
}
return button;
};
const leftContainer = document.createElement('div');
leftContainer.className = "md:flex text-center mb-2";
leftContainer.appendChild(createButton('Anterior', currentPage === 1, () => this.loadRegister(currentPage - 1), 'rounded-l-md'));
// Rango de páginas para mostrar
const maxVisibleButtons = 5; // Número máximo de botones a mostrar
const halfRange = Math.floor(maxVisibleButtons / 2);
let startPage = Math.max(1, currentPage - halfRange);
let endPage = Math.min(totalPages, currentPage + halfRange);
// Ajustar si estamos al inicio o al final del rango de páginas
if (currentPage <= halfRange) {
endPage = Math.min(totalPages, maxVisibleButtons);
}
if (currentPage + halfRange > totalPages) {
startPage = Math.max(1, totalPages - maxVisibleButtons + 1);
}
// Crear botones de paginación
for (let i = startPage; i <= endPage; i++) {
const pageButton = createButton(
i,
false,
() => this.loadRegister(i),
`mx-1 ${i === currentPage ? 'bg-black text-gray-50' : 'bg-white text-black'} rounded-md`
);
leftContainer.appendChild(pageButton);
}
leftContainer.appendChild(createButton('Siguiente', currentPage === totalPages, () => this.loadRegister(currentPage + 1), 'rounded-r-md'));
paginationButtonsContainer.appendChild(leftContainer);
const recordCountDiv = document.createElement('div');
const startRecord = (currentPage - 1) * 10 + 1;
const endRecord = Math.min(currentPage * 10, this.totalRecords);
recordCountDiv.className = "text-sm text-gray-700 text-center md:ml-auto";
recordCountDiv.innerHTML = `Mostrando ${startRecord} a ${endRecord} de ${this.totalRecords} registros`;
paginationButtonsContainer.appendChild(recordCountDiv);
paginationContainer.appendChild(paginationButtonsContainer);
},
closeModal() {
this.addModal = false;
this.editModal = false;
this.viewModal = false;
this.showConfirmModal = false;
this.newFile = null; // Reiniciar archivo
this.updateFile = null; // Reiniciar archivo
},
handleFileChange(event) {
this.newFile = event.target.files[0]; // Manejar el archivo seleccionado
},
handleUpdateFileChange(event) {
this.updateFile = event.target.files[0]; // Manejar el archivo seleccionado para la actualización
},
submitAdd() {
this.loading = true;
const formData = new FormData();
formData.append('description', this.newDescription);
if (this.newFile) formData.append('file', this.newFile); // Adjuntar el archivo si existe
fetch('/app/tarifas', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
this.newDescription = '';
this.newRuta = data.ruta; // Guardar la ruta del archivo en la respuesta
this.newFile = null; // Reinicia la variable de archivo
// Limpia manualmente el input de archivo en el DOM
document.getElementById('file').value = '';
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
submitEdit() {
this.loading = true;
const formData = new FormData();
formData.append('description', this.Description);
if (this.updateFile) formData.append('file', this.updateFile); // Adjuntar el archivo si existe
fetch(`/app/tarifas/${this.Id}`, {
method: 'PUT',
body: formData
})
.then(response => response.json())
.then(data => {
this.Description = '';
this.Ruta = data.ruta; // Actualizar la ruta del archivo en la respuesta
this.updateFile = null; // Reinicia la variable de archivo
// Limpia manualmente el input de archivo en el DOM
document.getElementById('editFile').value = '';
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
deleteItem() {
this.loading = true;
fetch(`/app/tarifas/${this.deleteId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
.then(() => {
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
}
}));
});
</script>
</div>
+19
View File
@@ -0,0 +1,19 @@
<!-- Container Principal -->
<div class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full ">
<h1 class="text-2xl font-bold mb-4">Token Vencido</h1>
<div class="justify-between w-full md:flex">
<p class="mb-4 text-sm">Tu token ha expirado. Por favor, intenta restablecer tu contraseña nuevamente.</p>
<a href="/request-password-reset"><button type="submit"
class="bg-[#8eb02f] text-white px-4 py-2 rounded flex space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
</svg>
<p> Intentar Restablecer Contraseña </p>
</button>
</a>
</div>
</div>
</div>
+303
View File
@@ -0,0 +1,303 @@
<!-- Container Principal -->
<div x-data="tramitesForm" class="bg-white rounded-lg shadow">
<div class="mx-auto py-8 px-4 sm:px-6 lg:px-8 max-w-5xl mb-8">
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<div class="section-title flex items-center mb-6">
<span class="w-1 h-10 bg-blue-800 mr-4"></span>
<h2 class="text-3xl font-bold ">
Gestión de Trámites
</h2>
</div>
<!-- Subtítulo -->
<h3 class="font-semibold text-xl mt-4">
PROCESO PARA ADQUIRIR EL SERVICIO DE GAS
</h3>
<p class="text-black mt-2">
Si a un no cuenta con el servicio de instalación del gas natural de Gases del Oriente ahora es más fácil. El
procedimiento a seguir es el siguiente:
</p>
<div class=" shadow-[0_5px_25px_rgba(0,0,0,0.1)] rounded-2xl p-6 mt-4">
<label class="block mb-2" for="desc_adquirir">Descripción de adquirir</label>
<textarea id="desc_adquirir" x-model="desc_adquirir" class="border border-gray-300 rounded w-full p-2 mb-4" required></textarea>
</div>
<!-- Section with 3 cards below the description -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
<!-- Card 1 -->
<div class=" shadow-[0_5px_25px_rgba(0,0,0,0.1)] rounded-2xl p-4 flex items-center">
<img src="/img/img38.webp" alt="Phone Icon" class="w-16 h-16 mr-4">
<p class="text-black">
<label class="block mb-2" for="paso_1">Paso 1</label>
<textarea id="paso_1" x-model="paso_1" class="border border-gray-300 rounded w-full p-2 mb-4" required></textarea>
</p>
</div>
<!-- Card 2 -->
<div class=" shadow-[0_5px_25px_rgba(0,0,0,0.1)] rounded-2xl p-4 flex items-center">
<img src="/img/img37.webp" alt="Letter Icon" class="w-16 h-16 mr-4">
<p class="text-black">
<label class="block mb-2" for="paso_2">Paso 2</label>
<textarea id="paso_2" x-model="paso_2" class="border border-gray-300 rounded w-full p-2 mb-4" required></textarea>
</p>
</div>
<!-- Card 3 -->
<div class=" shadow-[0_5px_25px_rgba(0,0,0,0.1)] rounded-2xl p-4 flex items-center">
<img src="/img/img36.webp" alt="Building Icon" class="w-16 h-16 mr-4">
<p class="text-black">
<label class="block mb-2" for="paso_3">Paso 3</label>
<textarea id="paso_3" x-model="paso_3" class="border border-gray-300 rounded w-full p-2 mb-4" required></textarea>
</p>
</div>
</div>
<p class="text-black mt-8">
Cuando la solicitud de instalación es para servicio residencial, la empresa programará la visita al bien inmueble
indicado, dentro de los ocho (8) días siguientes a la recepción de la solicitud, con el fin de verificar la
disponibilidad de red, que la vivienda cumpla con las condiciones técnicas y físicas para la viabilidad del
servicio. Si el inmueble cumple con dichas especificaciones, el asesor comercial procede a solicitar la
documentación requerida para la suscripción del contrato:
</p>
<div class="mt-8 mb-8 p-6 rounded-2xl shadow-[0_5px_25px_rgba(0,0,0,0.1)]">
<label class="block mb-2" for="doc_res">Documento Residencial</label>
<textarea id="doc_res" x-model="doc_res" class="border border-gray-300 rounded w-full p-2 mb-4" required></textarea>
</div>
<p class="text-black mt-8">
Si el servicio a instalar corresponde a un Usuario potencial No Residencial, se emitirá un presupuesto dentro de
los ocho (8) días siguientes a la visita el cual contendrá el valor de la instalación, y posterior a la
aceptación, se recibirá la documentación solicitada con el cual se suscribirá el contrato. Si usted cuenta con la
instalación interna debidamente construida por una firma constructora y reparadora inscrita ante la
Superintendencia de Industria y Comercio y ante nuestra empresa, será necesario que la solicitud del servicio sea
realizada únicamente en el canal presencial de nuestras oficinas y dar cumplimiento a los requisitos especificados
a continuación:
</p>
<div class="mt-8 mb-8 p-6 rounded-2xl shadow-[0_5px_25px_rgba(0,0,0,0.1)]">
<label class="block mb-2" for="doc_nores">Documento No Residencial</label>
<textarea id="doc_nores" x-model="doc_nores" class="border border-gray-300 rounded w-full p-2 mb-4" required></textarea>
</div>
<p class="text-black font-semibold">
Nota: GASES DEL ORIENTE S.A. E.S.P. informa que ningún funcionario de la compañía está autorizado para recibir
dinero o solicitar dádiva alguna para la instalación del servicio.
</p>
<!-- Formulario de Archivos -->
<div class="p-6 rounded-lg border w-full shadow mt-6 mb-4">
<h2 class="text-xl font-bold mb-2">Archivos</h2>
<label class="block mb-2" for="arc_venta_comp">Archivo de Venta Completa</label>
<input type="file" id="arc_venta_comp" @change="handleFileChange($event, 'arc_venta_comp')" class="border border-gray-300 rounded w-full p-2 mb-2" accept=".jpg,.png,.pdf,.doc,.docx">
<p x-show="arc_venta_comp_path" class="mt-2 text-sm text-gray-600">
Archivo actual: <a :href="`${window.location.origin}/${arc_venta_comp_path}`" target="_blank" class="text-blue-500 underline" x-text="arc_venta_comp_path"></a>
</p>
<label class="block mb-2" for="arc_derecho">Archivo de Derecho</label>
<input type="file" id="arc_derecho" @change="handleFileChange($event, 'arc_derecho')" class="border border-gray-300 rounded w-full p-2 mb-2" accept=".jpg,.png,.pdf,.doc,.docx">
<p x-show="arc_derecho_path" class="mt-2 text-sm text-gray-600">
Archivo actual: <a :href="`${window.location.origin}/${arc_derecho_path}`" target="_blank" class="text-blue-500 underline" x-text="arc_derecho_path"></a>
</p>
</div>
<div class="section-title flex items-center mb-6">
<span class="w-1 h-10 bg-blue-800 mr-4"></span>
<h2 class="text-3xl font-bold ">
Instructivo y carta para el diligenciamiento de la solicitud de disponibilidad de servicio.
</h2>
</div>
<!-- Subtitle Section -->
<h2 class="font-semibold text-lg text-[#5082e6] mb-4">
INSTRUCTIVO SOLICITUD DE DISPONIBILIDAD DE SERVICIO CARTA SOLICITUD DE DISPONIBILIDAD DE SERVICIO
</h2>
<label class="block mb-2" for="arc_instruct">Archivo de Instructivo</label>
<input type="file" id="arc_instruct" @change="handleFileChange($event, 'arc_instruct')" class="border border-gray-300 rounded w-full p-2 mb-2" accept=".jpg,.png,.pdf,.doc,.docx">
<p x-show="arc_instruct_path" class="mt-2 text-sm text-gray-600">
Archivo actual: <a :href="`${window.location.origin}/${arc_instruct_path}`" target="_blank" class="text-blue-500 underline" x-text="arc_instruct_path"></a>
</p>
<!-- Requirements Title -->
<h3 class="font-semibold text-xl mt-4 mb-4">
REQUISITOS PARA TERMINACIÓN Y ANULACIÓN DEL SERVICIO
</h3>
<!-- Content Section -->
<div class="flex flex-col lg:flex-row items-center lg:items-start lg:space-x-8 mb-8">
<!-- Left Text Content -->
<div class="lg:w-3/5 mb-6 lg:mb-0">
<div class=" p-6 rounded-2xl shadow-[0_5px_25px_rgba(0,0,0,0.1)]">
<label class="block mb-2" for="anul_servicio">Anulación de Servicio</label>
<textarea id="anul_servicio" x-model="anul_servicio" class="border border-gray-300 rounded w-full p-2 mb-4" required></textarea>
</div>
</div>
<!-- Right Image Content -->
<div class="lg:w-2/5">
<img src="/img/img39.webp" alt="Person writing on paper" class="mx-auto lg:mx-0" />
</div>
</div>
<h3 class="font-semibold text-xl mt-4 mb-4">
EN CASO DE ANULACIÓN DE SERVICIO
</h3>
<!-- Content Section -->
<p class="text-black mt-4">
Para <strong>GASES DEL ORIENTE S.A. E.S.P.</strong>, anular el contrato de prestación del servicio de gas natural
del inmueble antes señalado, es necesario que el titular presente la solicitud por escrito acompañada de una
fotocopia de la cédula de ciudadanía. En caso de presentar la anulación una persona diferente al suscriptor del
servicio, se debe acreditar propiedad sobre el inmueble y presentar la siguiente información:
</p>
<!-- Information Box -->
<div class="mt-4 bg-white p-6 rounded-2xl shadow-[0_5px_25px_rgba(0,0,0,0.1)]">
<label class="block mb-2" for="act_suscriptor">Actualizar Suscriptor</label>
<textarea id="act_suscriptor" x-model="act_suscriptor" class="border border-gray-300 rounded w-full p-2 mb-4" required></textarea>
</div>
<!-- Title Section -->
<h3 class="font-semibold text-xl mt-4 mb-4">
ACTUALIZACIÓN DE SUSCRIPTOR, ESTRATO O DIRECCIÓN DEL SERVICIO
</h3>
<!-- Content Section -->
<p class="text-black mt-4">
Los usuarios interesados en realizar la actualización de suscriptor, estrato o dirección del servicio de gas
combustible deberán presentarse en las oficinas de Gases del Oriente S.A. E.S.P. o enviar solicitud escrita
firmada por el o los propietarios del inmueble y presentar los siguientes documentos:
</p>
<!-- Documents List -->
<h3 class="text-lg mt-4">Algunos de estos documentos:</h3>
<div class="mt-4 p-6 rounded-2xl shadow-[0_5px_25px_rgba(0,0,0,0.1)]">
<label class="block mb-2" for="act_est_res">Actualizar Estado Res</label>
<textarea id="act_est_res" x-model="act_est_res" class="border border-gray-300 rounded w-full p-2 mb-4" required></textarea>
</div>
<!-- Title Section -->
<h3 class="font-semibold text-xl mt-4 mb-4">
DOCUMENTACIÓN REQUERIDA PARA ACTUALIZACIÓN DE ESTRATO
</h3>
<div class="mt-4 p-6 rounded-2xl shadow-[0_5px_25px_rgba(0,0,0,0.1)]">
<div class="flex items-start mb-4">
<p class="font-semibold">Para usuarios residenciales</p>
</div>
<label class="block mb-2" for="act_est_nores">Actualizar Estado No Residencial</label>
<textarea id="act_est_nores" x-model="act_est_nores" class="border border-gray-300 rounded w-full p-2 mb-4" required></textarea>
<div class="flex items-start mb-4 mt-6">
<p class="font-semibold">Para usuarios no residenciales</p>
</div>
<label class="block mb-2" for="act_direccion">Actualizar Dirección</label>
<textarea id="act_direccion" x-model="act_direccion" class="border border-gray-300 rounded w-full p-2 mb-4" required></textarea>
</div>
<!-- Botón de Guardar -->
<div class="mt-6 text-right">
<button @click.prevent="submitUpdate" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar Cambios</button>
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('tramitesForm', () => ({
desc_adquirir: '',
paso_1: '',
paso_2: '',
paso_3: '',
doc_res: '',
doc_nores: '',
anul_servicio: '',
act_suscriptor: '',
act_est_res: '',
act_est_nores: '',
act_direccion: '',
arc_venta_comp: null,
arc_venta_comp_path: '',
arc_derecho: null,
arc_derecho_path: '',
arc_instruct: null,
arc_instruct_path: '',
loading: false,
id_dato : 0,
init() {
this.loadData();
},
loadData() {
this.loading = true;
fetch('/loadtramites') // Llama al endpoint que devuelve el registro de trámites
.then(response => response.json())
.then(data => {
if (data) {
this.id_dato = data.ID;
this.desc_adquirir = data.desc_adquirir;
this.paso_1 = data.paso_1;
this.paso_2 = data.paso_2;
this.paso_3 = data.paso_3;
this.doc_res = data.doc_res;
this.doc_nores = data.doc_nores;
this.anul_servicio = data.anul_servicio;
this.act_suscriptor = data.act_suscriptor;
this.act_est_res = data.act_est_res;
this.act_est_nores = data.act_est_nores;
this.act_direccion = data.act_direccion;
this.arc_venta_comp_path = data.arc_venta_comp;
this.arc_derecho_path = data.arc_derecho;
this.arc_instruct_path = data.arc_instruct;
}
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
handleFileChange(event, field) {
if (field === 'arc_venta_comp') {
this.arc_venta_comp = event.target.files[0];
} else if (field === 'arc_derecho') {
this.arc_derecho = event.target.files[0];
} else if (field === 'arc_instruct') {
this.arc_instruct = event.target.files[0];
}
},
submitUpdate() {
this.loading = true;
const formData = new FormData();
formData.append('desc_adquirir', this.desc_adquirir);
formData.append('paso_1', this.paso_1);
formData.append('paso_2', this.paso_2);
formData.append('paso_3', this.paso_3);
formData.append('doc_res', this.doc_res);
formData.append('doc_nores', this.doc_nores);
formData.append('anul_servicio', this.anul_servicio);
formData.append('act_suscriptor', this.act_suscriptor);
formData.append('act_est_res', this.act_est_res);
formData.append('act_est_nores', this.act_est_nores);
formData.append('act_direccion', this.act_direccion);
if (this.arc_venta_comp) formData.append('arc_venta_comp', this.arc_venta_comp);
if (this.arc_derecho) formData.append('arc_derecho', this.arc_derecho);
if (this.arc_instruct) formData.append('arc_instruct', this.arc_instruct);
fetch('/app/tramites/'+this.id_dato, {
method: 'PUT',
body: formData
})
.then(response => response.json())
.then(() => {
this.loadData();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
}
}));
});
</script>
</div>
+561
View File
@@ -0,0 +1,561 @@
<!-- Container Principal -->
<div x-data="modals" @keydown.escape="closeModal" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full ">
<h1 class="text-2xl font-bold mb-4">Lista de Usuarios</h1>
<div class="justify-between w-full md:flex">
<p class="mb-4 text-sm">Gestión de usuarios.</p>
<!-- Campo de Búsqueda -->
<div class="mb-4 relative">
<input type="text" x-model="searchQuery" placeholder="Buscar..."
class="border border-gray-300 rounded w-full md:w-60 p-2 text-sm" @input="loadRegister()">
<!-- Contenedor para el botón o el ícono de carga -->
<div x-show="isLoading" class="absolute right-2 top-2 flex items-center">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
</svg>
</div>
</div>
<!-- Botón Nuevo Módulo -->
<div class="flex justify-end mb-4">
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded text-sm"
x-on:click="addModal=true">Nuevo</button>
</div>
</div>
<!-- Agregar el GIF de carga -->
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<div class="overflow-x-auto max-w-full">
<!-- Tabla de registros -->
<table class=" table-auto w-full">
<thead class="text-sm text-left py-4 border-b border-gray-300">
<tr class="text-left font-semibold border-collapse">
<th class="py-2 px-4 border-b w-52 " x-text=>Nombre</th>
<th class="py-2 px-4 border-b w-52 ">Usuario</th>
<th class="py-2 px-4 border-b">Estado</th>
<th class="py-2 px-4 border-b">Email</th>
<th class="py-2 px-4 border-b">Rol</th>
<th class="py-2 px-4 border-b">Verificado</th>
<th class="py-2 px-4 border-b w-32"></th>
</tr>
</thead>
<tbody class="text-gray-500 select-none text-sm ">
<template x-for="user in users" :key="user.ID">
<tr>
<td class="py-3 text-xs px-4 border-b" x-text="user.name"></td>
<td class="py-3 text-xs px-4 border-b" x-text="user.nombre_usuario"></td>
<td class="py-3 text-xs px-4 border-b">
<button class="w-9 h-5 rounded-full transition-colors duration-200"
:title="user.estado ? 'Activo' : 'Inactivo'"
:class="user.estado ? 'bg-green-500' : 'bg-gray-300'"
@click="toggleEstado(user.ID, user.estado)">
<div class="w-5 h-5 bg-white rounded-full shadow-gray-400 shadow transform ring-0 transition-transform duration-300"
:class="user.estado ? 'translate-x-full' : ''">
</div>
</button>
</td>
<td class="py-3 text-xs px-4 border-b" x-text="user.email"></td>
<td class="py-3 text-xs px-4 border-b" x-text="user.Role.name"></td>
<td class="py-3 text-xs px-4 border-b">
<template x-if="user.email_verified == true">
<div class="flex space-x-2">
<div class="w-2 h-2 rounded-full mr-1 align-middle my-auto bg-green-500"></div>
<span>Si</span>
</div>
<!-- Círculo verde -->
</template>
<template x-if="!user.email_verified">
<!-- Botón Enviar verificación -->
<button @click="enviarVerificación(user.ID)" title="Reenviar validación">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-orange-400">
<path stroke-linecap="round" stroke-linejoin="round"
d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
</svg>
</button>
</template>
</td>
<td class="py-3 text-xs px-4 border-b flex justify-between space-x-1">
<!-- Botón de Ver -->
<button @click="
viewModal = true;
Name = user.name;
NombreUsuario = user.nombre_usuario;
Estado = user.estado;
Email = user.email;
roleName = user.Role.name;
" title="Ver">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-[#8eb02f]">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<!-- Botón de Editar -->
<button @click="
editModal = true;
Id = user.ID;
Name = user.name;
NombreUsuario = user.nombre_usuario;
Estado = user.estado;
Email = user.email;
roleName = user.Role.ID;
" title="Editar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</button>
<!-- Botón de Eliminar -->
<button @click="showConfirmModal = true; deleteId = user.ID;" title="Eliminar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-red-500">
<path stroke-linecap="round" stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button>
</td>
</tr>
</template>
<!-- Mensaje de Cargando -->
<tr x-show="isLoading">
<td colspan="6" class="py-2 px-4 border-b text-center">
<div class="text-center flex justify-center">
<svg class="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z">
</path>
</svg>
Cargando registros...
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div>
<!-- Los botones de paginación se generarán aquí -->
<div id="pagination" class="flex justify-left my-4 text-sm">
</div>
<!-- Mostrar conteo de registros -->
<div id="recordCountDiv" class="text-sm text-gray-700 ml-4">
</div>
</div>
</div>
<!-- Modal Ver -->
<div x-cloak x-show="viewModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Ver registro</h2>
<p class="font-semibold" x-text="Name"></p> <!-- Muestra el nombre -->
<p class="mt-2" x-text="NombreUsuario"></p> <!-- Muestra el nombre de usuario -->
<p class="mt-2 flex items-center">
<!-- Punto de color basado en el estado -->
<span :class="Estado ? 'bg-green-500' : 'bg-gray-400'"
class="inline-block w-3 h-3 rounded-full mr-2"></span>
<!-- Texto del estado (puedes personalizarlo) -->
<span x-text="Estado ? 'Activo' : 'Inactivo'"></span>
</p>
<p class="mt-2" x-text="Email"></p> <!-- Muestra el correo -->
<p class="mt-2" x-text="roleName"></p> <!-- Muestra el nombre del rol -->
<button class="mt-4 bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="viewModal = false">Cerrar</button>
</div>
</div>
<!-- Modal Editar -->
<div x-cloak x-show="editModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Editar registro</h2>
<form @submit.prevent="submitEdit">
<!-- Nombre -->
<label class="block mb-2" for="editName">Nombre</label>
<input type="text" id="editName" x-model="Name" placeholder="Nombre"
class="border border-gray-300 rounded w-full p-2" required>
<!-- Nombre de usuario -->
<label class="block mt-4 mb-2" for="editUsername">Nombre de usuario</label>
<input id="editUsername" x-model="NombreUsuario" placeholder="Nombre de usuario"
class="border border-gray-300 rounded w-full p-2" required>
<!-- Email -->
<label class="block mt-4 mb-2" for="editEmail">Email</label>
<input id="editEmail" x-model="Email" placeholder="Email"
class="border border-gray-300 rounded w-full p-2" required>
<!-- Cambiar contraseña -->
<div class="bg-[#8eb02f] text-white px-4 py-2 mt-2 rounded text-center cursor-pointer"
x-on:click="showPasswordModal = true;" title="Cambiar contraseña">Cambiar contraseña</div>
<!-- Rol -->
<label class="block mt-4 mb-2" for="editRole">Rol</label>
<select id="editRole" x-model="roleName" class="border border-gray-300 rounded w-full p-2" required>
<option value="" disabled selected>Selecciona un rol</option> <!-- Opción predeterminada -->
<template x-for="role in roles" :key="role.ID">
<option :value="role.ID" x-text="role.name"></option>
</template>
</select>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar Cambios</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Añadir -->
<div x-cloak x-show="addModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Agregar registro</h2>
<form @submit.prevent="submitAdd">
<label class="block mb-2" for="name">Nombre</label>
<input type="text" id="name" x-model="newName" placeholder="Nombre"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="nombre_usuario">Nombre de usuario</label>
<input type="text" id="nombre_usuario" x-model="newNombreUsuario" placeholder="Nombre usuario"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="editEmail">Email</label>
<input id="editEmail" x-model="newEmail" placeholder="Email" type="email"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="editRole">Rol</label>
<select id="editRole" x-model="newRole" class="border border-gray-300 rounded w-full p-2" required>
<option value="" disabled selected>Selecciona un rol</option> <!-- Opción predeterminada -->
<template x-for="role in roles" :key="role.ID">
<option :value="role.ID" x-text="role.name"></option>
</template>
</select>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar </button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Confirmar Eliminación -->
<div x-cloak x-show="showConfirmModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-4">Confirmar Eliminación</h2>
<p>¿Estás seguro de que deseas eliminar este registro?</p>
<div class="mt-4 flex justify-end">
<button class="bg-red-500 text-white px-4 py-2 rounded mr-2" @click="deleteItem()">Eliminar</button>
<button @click="closeModal" class="bg-gray-600 text-white px-4 py-2 rounded">Cancelar</button>
</div>
</div>
</div>
<!-- Modal cambiar contraseña -->
<div x-cloak x-show="showPasswordModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<form @submit.prevent="changePass">
<label class="block mb-2" for="password">Nueva contraseña</label>
<input type="text" id="password" x-model="password" placeholder="Ingrese una contraseña"
class="border border-gray-300 rounded w-full p-2" required>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar </button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModalPassword">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('modals', () => ({
addModal: false,
editModal: false,
viewModal: false,
showConfirmModal: false,
showPasswordModal: false,
newName: '',
newEmail: '',
newRole: '',
newNombreUsuario: '',
Name: '',
NombreUsuario: '',
Estado: '',
NewEstado: '',
Email: '',
roleName: '',
password: '',
userId: '',
Id: null,
deleteId: null,
loading: false,
totalRecords: 0, // Agregar esta línea
isLoading: false,
roles: '',
init() {
this.loadRegister();
},
//CARGAR registros
searchQuery: '', // Agrega esta línea dentro del objeto Alpine
// Modifica la función loadRegister para incluir la búsqueda
loadRegister(page = 1) {
this.isLoading = true;
//this.loading = true;
const search = this.searchQuery ? `&search=${this.searchQuery}` : '';
fetch(`/app/loadusers?page=${page}${search}`)
.then(response => response.json())
.then(data => {
this.totalRecords = data.total; // Establece el total de registros aquí
this.users = data.Users; // Asignar
this.roles = data.Roles;
console.log(this.roles);
this.totalPages = data.totalPages; // Asignar total de páginas
this.createPagination(this.totalPages, page);
if (data.Users.length != 0) {
// Lógica de paginación
const totalPages = Math.ceil(this.totalRecords / data.limit);
this.createPagination(totalPages, page)
}
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.isLoading = false; // Ocultar el GIF de carga
});
},
createPagination(totalPages, currentPage) {
const paginationContainer = document.getElementById('pagination');
paginationContainer.innerHTML = ''; // Limpiar la paginación actual
// Crear un contenedor para los botones de paginación
const paginationButtonsContainer = document.createElement('div');
paginationButtonsContainer.className = "md:flex mx-auto mb-2 md:justify-between items-center w-full"; // Alinear los botones
// Función para crear un botón
const createButton = (text, isDisabled, onClick, additionalClasses = '') => {
const button = document.createElement('button');
button.textContent = text;
button.disabled = isDisabled;
button.className = `px-4 py-2 text-black bg-gray-200 ${additionalClasses} ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-300'}`;
if (!isDisabled) {
button.onclick = onClick;
}
return button;
};
// Botones de paginación
const leftContainer = document.createElement('div');
leftContainer.className = "md:flex text-center mb-2"; // Contenedor para los botones de paginación a la izquierda
leftContainer.appendChild(createButton('Anterior', currentPage === 1, () => this.loadRegister(currentPage - 1), 'rounded-l-md'));
for (let i = 1; i <= totalPages; i++) {
const pageButton = createButton(i, false, () => this.loadRegister(i), `mx-1 ${i === currentPage ? 'bg-black text-gray-50' : 'bg-white text-black'} rounded-md`);
leftContainer.appendChild(pageButton);
}
leftContainer.appendChild(createButton('Siguiente', currentPage === totalPages, () => this.loadRegister(currentPage + 1), 'rounded-r-md'));
paginationButtonsContainer.appendChild(leftContainer);
// Agregar el conteo de registros a la derecha
const recordCountDiv = document.createElement('div');
const startRecord = (currentPage - 1) * 10 + 1; // Primer registro de la página actual
const endRecord = Math.min(currentPage * 10, this.totalRecords); // Último registro mostrado
recordCountDiv.className = "text-sm text-gray-700 text-center md:ml-auto"; // Sin margen para alineación a la izquierda
recordCountDiv.innerHTML = `Mostrando ${startRecord} a ${endRecord} de ${this.totalRecords} registros`;
paginationButtonsContainer.appendChild(recordCountDiv);
// Agregar los botones de paginación al contenedor principal
paginationContainer.appendChild(paginationButtonsContainer);
},
//CERRAR MODALES
closeModal() {
this.addModal = false;
this.editModal = false;
this.viewModal = false;
this.showConfirmModal = false;
},
//CERRAR MODAL PASS
closeModalPassword() {
this.showPasswordModal = false;
},
//CREAR
submitAdd() {
this.loading = true;
fetch('/app/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: this.newName,
nombre_usuario: this.newNombreUsuario,
email: this.newEmail,
role_id: parseInt(this.newRole),
estado: true,
tipo: 'sistema'
})
})
.then(response => response.json())
.then(() => {
this.newName = '';
this.newNombreUsuario = '';
this.newEmail = '';
this.newRole = '';
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
//ACTUALIZAR
submitEdit() {
this.loading = true;
fetch(`/app/users/${this.Id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: this.Name, // Usar 'Name' en lugar de 'First_name'
nombre_usuario: this.NombreUsuario, // Usar 'Nombre_usuario' en lugar de 'Last_name'
email: this.Email, // Usar 'Email'
role_id: parseInt(this.roleName) // Usar 'roleName'
})
})
.then(response => response.json())
.then(() => {
// Limpiar campos después de la actualización
this.Name = '';
this.NombreUsuario = '';
this.Email = '';
this.roleName = '';
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
//ACTUALIZAR PASS
changePass() {
this.loading = true;
fetch(`/app/password/${this.Id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
password: this.password,
})
})
.then(response => response.json())
.then(() => {
this.password = '';
this.closeModalPassword();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
//TOGGLE ESTADO
toggleEstado($id, $estado) {
this.Id = $id;
this.NewEstado = $estado;
this.loading = true;
if (this.NewEstado) {
this.NewEstado = false;
} else {
this.NewEstado = true;
}
console.log(this.NewEstado);
fetch(`/app/users/${this.Id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
estado: this.NewEstado
})
})
.then(response => response.json())
.then(() => {
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
//ELIMINAR
deleteItem() {
this.loading = true;
fetch(`/app/users/${this.deleteId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
.then(() => {
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
enviarVerificación($id) {
this.Id = $id;
this.loading = true;
fetch(`/app/verificacion/${this.Id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
})
.then(() => {
alert('Se ha enviado un correo de verificación');
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
}
}));
});
</script>
</div>
+615
View File
@@ -0,0 +1,615 @@
<!-- Container Principal -->
<div x-data="modals" @keydown.escape="closeModal" class="bg-white rounded-lg shadow">
<div class="container mx-auto p-6 w-full ">
<h1 class="text-2xl font-bold mb-4">Lista de Usuarios GAS</h1>
<div class="justify-between w-full md:flex">
<p class="mb-4 text-sm">Gestión de usuarios GAS.</p>
<!-- Campo de Búsqueda -->
<div class="mb-4 relative">
<input type="text" x-model="searchQuery" placeholder="Buscar..."
class="border border-gray-300 rounded w-full md:w-60 p-2 text-sm" @input="loadRegister()">
<!-- Contenedor para el botón o el ícono de carga -->
<div x-show="isLoading" class="absolute right-2 top-2 flex items-center">
<!-- SVG de cargando (puedes reemplazarlo con un botón si lo prefieres) -->
<svg class="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
</svg>
</div>
</div>
<!-- Botón Nuevo Módulo -->
<div class="flex justify-end mb-4">
<button class="bg-[#8eb02f] text-white px-4 py-2 rounded text-sm"
x-on:click="addModal=true">Nuevo</button>
</div>
</div>
<!-- Agregar el GIF de carga -->
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<div class="overflow-x-auto max-w-full">
<!-- Tabla de registros -->
<table class=" table-auto w-full">
<thead class="text-sm text-left py-4 border-b border-gray-300">
<tr class="text-left font-semibold border-collapse">
<th class="py-2 px-4 border-b w-52 ">Nombre</th>
<th class="py-2 px-4 border-b">Codigo</th>
<th class="py-2 px-4 border-b w-52">Medidor</th>
<th class="py-2 px-4 border-b">Estado</th>
<th class="py-2 px-4 border-b">Documento</th>
<th class="py-2 px-4 border-b">Email</th>
<th class="py-2 px-4 border-b">Celular</th>
<th class="py-2 px-4 border-b">Acepto</th>
<th class="py-2 px-4 border-b">Verificado</th>
<th class="py-2 px-4 border-b w-32"></th>
</tr>
</thead>
<tbody class="text-gray-500 select-none text-sm ">
<template x-for="user in users" :key="user.ID">
<tr>
<td class="py-3 text-xs px-4 border-b" x-text="user.name"></td>
<td class="py-3 text-xs px-4 border-b" x-text="user.nombre_usuario"></td>
<td class="py-3 text-xs px-4 border-b" x-text="user.numero_medidor || 'N/A'"></td>
<td class="py-3 text-xs px-4 border-b">
<button class="w-9 h-5 rounded-full transition-colors duration-200"
:title="user.estado ? 'Activo' : 'Inactivo'"
:class="user.estado ? 'bg-green-500' : 'bg-gray-300'"
@click="toggleEstado(user.ID, user.estado)">
<div class="w-5 h-5 bg-white rounded-full shadow-gray-400 shadow transform ring-0 transition-transform duration-300"
:class="user.estado ? 'translate-x-full' : ''">
</div>
</button>
</td>
<td class="py-3 text-xs px-4 border-b" x-text="user.documento"></td>
<td class="py-3 text-xs px-4 border-b" x-text="user.email"></td>
<td class="py-3 text-xs px-4 border-b" x-text="user.celular"></td>
<td class="py-3 text-xs px-4 border-b ">
<div class="flex j space-x-2 ">
<!-- Punto de color basado en acepto_info -->
<span :class="user.acepto_info ? 'bg-green-500' : 'bg-gray-400'"
class=" w-2 h-2 rounded-full mr-1 align-middle my-auto"></span>
<!-- Texto del estado -->
<span x-text="user.acepto_info ? 'Si' : 'No'"></span>
</div>
</td>
<td class="py-3 text-xs px-4 border-b">
<template x-if="user.email_verified == true">
<div class="flex space-x-2">
<div
class="w-2 h-2 rounded-full mr-1 align-middle my-auto bg-green-500"></div ><span>Si</span>
</div>
<!-- Círculo verde -->
</template>
<template x-if="!user.email_verified">
<!-- Botón Enviar verificación -->
<button @click="enviarVerificación(user.ID)" title="Reenviar validación">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-orange-400">
<path stroke-linecap="round" stroke-linejoin="round"
d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
</svg>
</button>
</template>
</td>
<td class="py-3 text-xs px-4 border-b flex justify-between space-x-1">
<!-- Botón de Ver -->
<button @click="
viewModal = true;
Name = user.name;
NombreUsuario = user.nombre_usuario;
NumeroMedidor = user.numero_medidor;
Estado = user.estado;
Email = user.email;
Celular = user.celular;
" title="Ver">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-[#8eb02f]">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<!-- Botón de Editar -->
<button @click="
editModal = true;
Id = user.ID;
Name = user.name;
NombreUsuario = user.nombre_usuario;
NumeroMedidor = user.numero_medidor;
Estado = user.estado;
Email = user.email;
Celular = user.celular;
" title="Editar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</button>
<!-- Botón de Eliminar -->
<button @click="showConfirmModal = true; deleteId = user.ID;" title="Eliminar">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 text-red-500">
<path stroke-linecap="round" stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button>
</td>
</tr>
</template>
<!-- Mensaje de Cargando -->
<tr x-show="isLoading">
<td colspan="6" class="py-2 px-4 border-b text-center">
<div class="text-center flex justify-center">
<svg class="animate-spin h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"
stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z">
</path>
</svg>
Cargando registros...
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div>
<!-- Los botones de paginación se generarán aquí -->
<div id="pagination" class="flex justify-left my-4 text-sm">
</div>
<!-- Mostrar conteo de registros -->
<div id="recordCountDiv" class="text-sm text-gray-700 ml-4">
</div>
</div>
</div>
<!-- Modal Ver -->
<div x-cloak x-show="viewModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Ver registro</h2>
<p class="font-semibold" x-text="Name"></p> <!-- Muestra el nombre -->
<p class="mt-2" x-text="NombreUsuario"></p> <!-- Muestra el nombre de usuario -->
<p class="mt-2 flex items-center">
<!-- Punto de color basado en el estado -->
<span :class="Estado ? 'bg-green-500' : 'bg-gray-400'"
class="inline-block w-3 h-3 rounded-full mr-2"></span>
<!-- Texto del estado (puedes personalizarlo) -->
<span x-text="Estado ? 'Activo' : 'Inactivo'"></span>
</p>
<p class="mt-2" x-text="Email"></p> <!-- Muestra el correo -->
<p class="mt-2" x-text="Celular"></p> <!-- Muestra el nombre del rol -->
<button class="mt-4 bg-[#8eb02f] text-white px-4 py-2 rounded"
x-on:click="viewModal = false">Cerrar</button>
</div>
</div>
<!-- Modal Editar -->
<div x-cloak x-show="editModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Editar registro</h2>
<form @submit.prevent="submitEdit">
<!-- Nombre -->
<label class="block mb-2" for="editName">Nombre</label>
<input type="text" id="editName" x-model="Name" placeholder="Nombre"
class="border border-gray-300 rounded w-full p-2" required>
<!-- Nombre de usuario -->
<label class="block mt-4 mb-2" for="editUsername">Codigo</label>
<input id="editUsername" x-model="NombreUsuario" placeholder="Codigo"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="editNumeroMedidor">Medidor</label>
<input id="editNumeroMedidor" x-model="NumeroMedidor" placeholder="Medidor"
class="border border-gray-300 rounded w-full p-2" required>
<!-- Email -->
<label class="block mt-4 mb-2" for="editEmail">Email</label>
<input id="editEmail" x-model="Email" placeholder="Email"
class="border border-gray-300 rounded w-full p-2" required>
<!-- Cambiar contraseña -->
<div class="bg-[#8eb02f] text-white px-4 py-2 mt-2 rounded text-center cursor-pointer"
x-on:click="showPasswordModal = true;" title="Cambiar contraseña">Cambiar contraseña</div>
<!-- Celular -->
<label class="block mt-4 mb-2" for="editCelular">Celular</label>
<input id="editCelular" x-model="Celular" placeholder="tel"
class="border border-gray-300 rounded w-full p-2" required>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar Cambios</button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Añadir -->
<div x-cloak x-show="addModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-2">Agregar registro</h2>
<form @submit.prevent="submitAdd">
<label class="block mb-2" for="name">Nombre</label>
<input type="text" id="name" x-model="newName" placeholder="Nombre"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="nombre_usuario">Codigo</label>
<input type="text" id="nombre_usuario" x-model="newNombreUsuario" placeholder="Codigo"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="numero_medidor">Medidor</label>
<input type="text" id="numero_medidor" x-model="newNumeroMedidor" placeholder="Medidor"
class="border border-gray-300 rounded w-full p-2">
<label class="block mt-4 mb-2" for="documento">Documento</label>
<input type="text" id="documento" x-model="newDocumento" placeholder="Documento"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="editEmail">Email</label>
<input id="editEmail" x-model="newEmail" placeholder="Email" type="email"
class="border border-gray-300 rounded w-full p-2" required>
<label class="block mt-4 mb-2" for="editCelular">Celular</label>
<input id="editCelular" x-model="newCelular" placeholder="Celular" type="tel"
class="border border-gray-300 rounded w-full p-2" required>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar </button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModal">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal Confirmar Eliminación -->
<div x-cloak x-show="showConfirmModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<h2 class="text-xl font-bold mb-4">Confirmar Eliminación</h2>
<p>¿Estás seguro de que deseas eliminar este registro?</p>
<div class="mt-4 flex justify-end">
<button class="bg-red-500 text-white px-4 py-2 rounded mr-2" @click="deleteItem()">Eliminar</button>
<button @click="closeModal" class="bg-gray-600 text-white px-4 py-2 rounded">Cancelar</button>
</div>
</div>
</div>
<!-- Modal cambiar contraseña -->
<div x-cloak x-show="showPasswordModal" x-transition
class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-40 justify-center items-center text-sm">
<div class="bg-white p-6 rounded shadow-lg max-w-sm w-full overflow-y-auto max-h-full">
<form @submit.prevent="changePass">
<label class="block mb-2" for="password">Nueva contraseña</label>
<input type="text" id="password" x-model="password" placeholder="Ingrese una contraseña"
class="border border-gray-300 rounded w-full p-2" required>
<div class="mt-4 flex justify-between">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar </button>
<button type="button" class="bg-gray-600 text-white px-4 py-2 rounded"
x-on:click="closeModalPassword">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('modals', () => ({
addModal: false,
editModal: false,
viewModal: false,
showConfirmModal: false,
showPasswordModal: false,
users : [],
newName: '',
newEmail: '',
newNombreUsuario: '',
newCelular: '',
Name: '',
NombreUsuario: '',
NumeroMedidor: '',
Estado: '',
NewEstado: '',
Email: '',
Celular: '',
newDocumento: '',
newNumeroMedidor: '',
newCelular: '',
password: '',
userId: '',
Id: null,
deleteId: null,
loading: false,
totalRecords: 0, // Agregar esta línea
isLoading: false,
roles: '',
init() {
this.loadRegister();
},
//CARGAR registros
searchQuery: '', // Agrega esta línea dentro del objeto Alpine
// Modifica la función loadRegister para incluir la búsqueda
loadRegister(page = 1) {
this.isLoading = true;
//this.loading = true;
const search = this.searchQuery ? `&search=${this.searchQuery}` : '';
fetch(`/app/loadusers_gas?page=${page}${search}`)
.then(response => response.json())
.then(data => {
console.log(data);
this.totalRecords = data.total; // Establece el total de registros aquí
this.users = data.Users; // Asignar
this.roles = data.Roles;
this.totalPages = data.totalPages; // Asignar total de páginas
this.createPagination(this.totalPages, page);
if (data.Users.length != 0) {
// Lógica de paginación
const totalPages = Math.ceil(this.totalRecords / data.limit);
this.createPagination(totalPages, page)
}
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.isLoading = false; // Ocultar el GIF de carga
});
},
createPagination(totalPages, currentPage) {
const paginationContainer = document.getElementById('pagination');
paginationContainer.innerHTML = ''; // Limpiar la paginación actual
// Crear un contenedor para los botones de paginación
const paginationButtonsContainer = document.createElement('div');
paginationButtonsContainer.className = "md:flex mx-auto mb-2 md:justify-between items-center w-full"; // Alinear los botones
// Función para crear un botón
const createButton = (text, isDisabled, onClick, additionalClasses = '') => {
const button = document.createElement('button');
button.textContent = text;
button.disabled = isDisabled;
button.className = `px-4 py-2 text-black bg-gray-200 ${additionalClasses} ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-300'}`;
if (!isDisabled) {
button.onclick = onClick;
}
return button;
};
// Botones de paginación
const leftContainer = document.createElement('div');
leftContainer.className = "md:flex text-center mb-2"; // Contenedor para los botones de paginación a la izquierda
leftContainer.appendChild(createButton('Anterior', currentPage === 1, () => this.loadRegister(currentPage - 1), 'rounded-l-md'));
// Rango de páginas para mostrar
const maxVisibleButtons = 5; // Número máximo de botones a mostrar
const halfRange = Math.floor(maxVisibleButtons / 2);
let startPage = Math.max(1, currentPage - halfRange);
let endPage = Math.min(totalPages, currentPage + halfRange);
// Ajustar si estamos al inicio o al final del rango de páginas
if (currentPage <= halfRange) {
endPage = Math.min(totalPages, maxVisibleButtons);
}
if (currentPage + halfRange > totalPages) {
startPage = Math.max(1, totalPages - maxVisibleButtons + 1);
}
// Crear botones de paginación
for (let i = startPage; i <= endPage; i++) {
const pageButton = createButton(
i,
false,
() => this.loadRegister(i),
`mx-1 ${i === currentPage ? 'bg-black text-gray-50' : 'bg-white text-black'} rounded-md`
);
leftContainer.appendChild(pageButton);
}
leftContainer.appendChild(createButton('Siguiente', currentPage === totalPages, () => this.loadRegister(currentPage + 1), 'rounded-r-md'));
paginationButtonsContainer.appendChild(leftContainer);
// Agregar el conteo de registros a la derecha
const recordCountDiv = document.createElement('div');
const startRecord = (currentPage - 1) * 10 + 1; // Primer registro de la página actual
const endRecord = Math.min(currentPage * 10, this.totalRecords); // Último registro mostrado
recordCountDiv.className = "text-sm text-gray-700 text-center md:ml-auto"; // Sin margen para alineación a la izquierda
recordCountDiv.innerHTML = `Mostrando ${startRecord} a ${endRecord} de ${this.totalRecords} registros`;
paginationButtonsContainer.appendChild(recordCountDiv);
// Agregar los botones de paginación al contenedor principal
paginationContainer.appendChild(paginationButtonsContainer);
},
//CERRAR MODALES
closeModal() {
this.addModal = false;
this.editModal = false;
this.viewModal = false;
this.showConfirmModal = false;
},
//CERRAR MODAL PASS
closeModalPassword() {
this.showPasswordModal = false;
},
//CREAR
submitAdd() {
this.loading = true;
fetch('/users_gas', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
nombre_usuario: this.newNombreUsuario,
numero_medidor: this.newNumeroMedidor,
documento: this.newDocumento,
name: this.newName,
email: this.newEmail,
celular: this.newCelular,
estado: true,
tipo: 'gas'
})
})
.then(response => response.json())
.then(() => {
this.newName = '';
this.newDocumento = '';
this.newNombreUsuario = '';
this.newNumeroMedidor = '';
this.newEmail = '';
this.newCelular = '';
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
//ACTUALIZAR
submitEdit() {
this.loading = true;
fetch(`/app/users_gas/${this.Id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: this.Name,
nombre_usuario: this.NombreUsuario,
numero_medidor: this.NumeroMedidor,
email: this.Email,
celular: this.Celular
})
})
// .then(response => response.json())
.then(() => {
// Limpiar campos después de la actualización
this.Name = '';
this.NombreUsuario = '';
this.NumeroMedidor = '';
this.Email = '';
this.Celular = '';
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
//ACTUALIZAR PASS
changePass() {
this.loading = true;
fetch(`/app/password/${this.Id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
password: this.password,
})
})
.then(response => response.json())
.then(() => {
this.password = '';
this.closeModalPassword();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
//TOGGLE ESTADO
toggleEstado($id, $estado) {
this.Id = $id;
this.NewEstado = $estado;
this.loading = true;
if (this.NewEstado) {
this.NewEstado = false;
} else {
this.NewEstado = true;
}
console.log(this.NewEstado);
fetch(`/app/users/${this.Id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
estado: this.NewEstado
})
})
.then(response => response.json())
.then(() => {
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
//ELIMINAR
deleteItem() {
this.loading = true;
fetch(`/app/users/${this.deleteId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
.then(() => {
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
},
enviarVerificación($id) {
this.Id = $id;
this.loading = true;
fetch(`/app/verificacion/${this.Id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
})
.then(() => {
alert('Se ha enviado un correo de verificación');
this.closeModal();
this.loadRegister();
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false; // Ocultar el GIF de carga
});
}
}));
});
</script>
</div>
+123
View File
@@ -0,0 +1,123 @@
<!-- Contenedor Principal -->
<div x-data="videoinstitucionalForm" class="bg-white mx-3 my-3 rounded-lg p-6">
<h1 class="text-2xl font-bold mb-4">Gestión de Video Institucional</h1>
<p class="mb-4 text-sm">Actualiza el archivo del video institucional.</p>
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex z-50 justify-center items-center">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<!-- Contenedor del Formulario -->
<div class="bg-white p-6 rounded-lg border shadow max-w-2xl mx-auto">
<!-- Alerta -->
<div x-show="showAlert" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span x-text="alertMessage"></span>
</div>
<form @submit.prevent="submitUpdate">
<!-- Campo para el archivo Video Institucional -->
<div class="mb-6">
<label class="block mb-2" for="video_institucional">Archivo Video Institucional</label>
<input type="file" id="video_institucional" @change="handleFileChange($event, 'video_institucional')" class="border border-gray-300 rounded w-full p-2" accept=".mp4" />
<p x-show="video_institucional_path" class="mt-2 text-sm text-gray-600">
Archivo actual: <a :href="`${window.location.origin}/${video_institucional_path}`" target="_blank" class="text-blue-500 underline" x-text="video_institucional_path"></a>
</p>
</div>
<!-- Botón de Guardar -->
<div class="mt-6 text-right">
<button type="submit" class="bg-[#8eb02f] text-white px-4 py-2 rounded">Guardar Cambios</button>
</div>
</form>
</div>
<!-- Alpine.js Script -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('videoinstitucionalForm', () => ({
// Campos relacionados con el archivo
video_institucional: null,
video_institucional_path: '',
loading: false,
id_dato : 0,
showAlert: false,
alertMessage: '',
init() {
this.loadData();
},
loadData() {
this.loading = true;
fetch('/loadvideoinstitucional') // Cambia esta URL al endpoint correspondiente
.then(response => response.json())
.then(data => {
if (data && data.video_institucional) {
this.id_dato = data.ID;
this.video_institucional_path = data.video_institucional; // Ruta del archivo actual
}
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
},
handleFileChange(event) {
const file = event.target.files[0];
if (!file) return;
// Validar que sea MP4
const allowedTypes = ['video/mp4'];
if(!allowedTypes.includes(file.type)) {
this.showAlert = true;
this.alertMessage = 'El archivo debe ser un video MP4.';
this.video_institucional = null;
event.target.value = ''; // Reset input
return;
}
// Validar tamaño del archivo (ej: máx 50MB)
const maxSize = 50 * 1024 * 1024; // 50MB
if(file.size > maxSize) {
this.showAlert = true;
this.alertMessage = 'El archivo no debe exceder los 50MB.';
this.video_institucional = null;
event.target.value = ''; // Reset input
return;
}
// Si pasa las validaciones, asignar el archivo y cerrar alerta
this.showAlert = false;
this.alertMessage = '';
this.video_institucional = file;
},
submitUpdate() {
this.loading = true;
const formData = new FormData();
// Validar si hay un archivo nuevo
if (this.video_institucional) {
formData.append('video_institucional', this.video_institucional);
} else if (this.video_institucional_path) {
// Si no hay archivo nuevo, enviar el valor actual
formData.append('video_institucional', this.video_institucional_path);
}
fetch('/app/videoinstitucional/'+this.id_dato, { // Cambia esta URL al endpoint correspondiente
method: 'PUT',
body: formData
})
.then(response => response.json())
.then(() => {
this.loadData(); // Recargar datos después de la actualización
})
.catch(error => console.error('Error:', error))
.finally(() => {
this.loading = false;
});
}
}));
});
</script>
</div>