This commit is contained in:
Felipe
2025-02-07 12:09:47 -05:00
parent 206aea72ed
commit 27e7292f5f
20 changed files with 23 additions and 1284 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 12 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

+15 -14
View File
@@ -1,17 +1,18 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gases del oriente</title>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>U-site</title>
<script type="module" crossorigin src="/assets/index-BmU8V2V2.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CAkfvDkj.css">
</head>
<body>
<div id="app"></div>
</body>
</head>
<body>
<div id="app"></div>
</body>
</html>
+2 -2
View File
@@ -26,7 +26,7 @@ func SendPasswordResetEmail(email string, baseURL string) {
// Ejecutar la goroutine para enviar el correo de manera asíncrona
go func() {
err := app.Http.Mail.Send(email, "Solicitud de restablecimiento de contraseña - Gases del oriente", htmlBody, "")
err := app.Http.Mail.Send(email, "Solicitud de restablecimiento de contraseña - U-site", htmlBody, "")
errChan <- err // Enviar el error (o nil si no hay error) al canal
}()
@@ -51,7 +51,7 @@ func SendConfirmationEmail(email string, baseURL string, nombreUsuario string) {
// Ejecutar la goroutine para enviar el correo de manera asíncrona
go func() {
err := app.Http.Mail.Send(email, "✅ ¡Confirma tu registro en la Oficina Virtual de Gases del Oriente!", htmlBody, "")
err := app.Http.Mail.Send(email, "✅ ¡Confirma tu registro en la Oficina Virtual de U-site!", htmlBody, "")
errChan <- err // Enviar el error (o nil si no hay error) al canal
}()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

+1 -1
View File
@@ -14,6 +14,6 @@
<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 🚀
El equipo de U-site 🚀
</div>
</div>
+1 -1
View File
@@ -10,6 +10,6 @@
</div>
<div class="footer-note">
📌 ¿No realizaste esta solicitud? Ignora este correo..<br>
El equipo de Gases del Oriente 🚀
El equipo de U-site 🚀
</div>
</div>
+1 -1
View File
@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gases del Oriente</title>
<title>U-site</title>
<!-- Alpine.js -->
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<!-- TailwindCSS -->
+1 -1
View File
@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gases del Oriente</title>
<title>U-site</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>
+1 -1
View File
@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gases del Oriente</title>
<title>U-site</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>
+1 -1
View File
@@ -55,7 +55,7 @@
switch (result.status) {
case "success":
window.location.href = '/app/dashboard';
window.location.href = '/app/users';
break;
case "missing":
-289
View File
@@ -1,289 +0,0 @@
<!-- 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
@@ -1,151 +0,0 @@
<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>
-396
View File
@@ -1,396 +0,0 @@
<!-- 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>
-303
View File
@@ -1,303 +0,0 @@
<!-- 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>
-123
View File
@@ -1,123 +0,0 @@
<!-- 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>