ADD: vistas conceptos, multas, descuentos y membresias
This commit is contained in:
parent
43eb9226e6
commit
69937297dc
58
src/components/App/AddressSection.vue
Normal file
58
src/components/App/AddressSection.vue
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
/** Eventos */
|
||||||
|
const emit = defineEmits(['address-created']);
|
||||||
|
|
||||||
|
/** Refs */
|
||||||
|
const addressName = ref("");
|
||||||
|
|
||||||
|
/** Métodos */
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (!addressName.value.trim()) {
|
||||||
|
alert("Por favor ingresa un nombre de dirección");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emitir evento al padre con la nueva dirección
|
||||||
|
emit('address-created', {
|
||||||
|
description: addressName.value
|
||||||
|
});
|
||||||
|
|
||||||
|
// Limpiar el formulario
|
||||||
|
addressName.value = "";
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-white rounded-xl p-6 m-3 max-w-auto shadow-lg mb-10">
|
||||||
|
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
||||||
|
Crear nueva dirección
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<form @submit.prevent="handleSubmit">
|
||||||
|
<div class="mb-5">
|
||||||
|
<label
|
||||||
|
for="addressName"
|
||||||
|
class="block text-sm text-gray-600 font-medium mb-2"
|
||||||
|
>
|
||||||
|
Nombre de la Dirección:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="adddressName"
|
||||||
|
v-model="addressName"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 flex justify-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-1/4 bg-[#7a0b3a] hover:bg-[#68082e] text-white font-medium py-3.5 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Guardar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
138
src/components/App/ConceptSection.vue
Normal file
138
src/components/App/ConceptSection.vue
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useForm } from "@Services/Api";
|
||||||
|
|
||||||
|
import Input from "@Holos/Form/Input.vue";
|
||||||
|
import Textarea from "@Holos/Form/Textarea.vue";
|
||||||
|
import Selectable from "@Holos/Form/Selectable.vue";
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
address: [],
|
||||||
|
shortName: "",
|
||||||
|
name: "",
|
||||||
|
legal: "",
|
||||||
|
article: "",
|
||||||
|
description: "",
|
||||||
|
minimumAmount: "",
|
||||||
|
maximumAmount: "",
|
||||||
|
sizeUnit: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const addresses = ref([]);
|
||||||
|
const sizeUnit = ref([]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-white rounded-xl p-6 m-3 max-w-auto shadow-lg mb-10">
|
||||||
|
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
||||||
|
Crear nuevo concepto de cobro
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<div class="mb-5 grid grid-cols-2 gap-4">
|
||||||
|
<Selectable
|
||||||
|
v-model="form.address"
|
||||||
|
label="description"
|
||||||
|
title="Direcciones"
|
||||||
|
:options="addresses"
|
||||||
|
multiple
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="form.shortName"
|
||||||
|
class="col-span-2"
|
||||||
|
id="Nombre corto"
|
||||||
|
type="text"
|
||||||
|
:onError="form.errors.shortName"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
v-model="form.name"
|
||||||
|
class="col-span-2"
|
||||||
|
id="Nombre"
|
||||||
|
type="text"
|
||||||
|
:onError="form.errors.name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="mb-5 grid grid-cols-2 gap-4 py-2">
|
||||||
|
<Input
|
||||||
|
v-model="form.legal"
|
||||||
|
class="col-span-2"
|
||||||
|
id="Instrumento Legal"
|
||||||
|
type="text"
|
||||||
|
:onError="form.errors.legal"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="form.article"
|
||||||
|
class="col-span-2"
|
||||||
|
id="Articulado"
|
||||||
|
type="text"
|
||||||
|
:onError="form.errors.article"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Textarea
|
||||||
|
v-model="form.description"
|
||||||
|
class="col-span-2"
|
||||||
|
id="Descripción"
|
||||||
|
:onError="form.errors.description"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="mb-5 grid grid-cols-2 gap-4 py-2">
|
||||||
|
<Input
|
||||||
|
v-model="form.minimumAmount"
|
||||||
|
class="col-span-2"
|
||||||
|
id="Monto mínimo (UMAs)"
|
||||||
|
type="text"
|
||||||
|
:onError="form.errors.minimumAmount"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="form.maximumAmount"
|
||||||
|
class="col-span-2"
|
||||||
|
id="Monto máximo (UMAs)"
|
||||||
|
type="text"
|
||||||
|
:onError="form.errors.maximumAmount"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<hr class="my-4 border-gray-300" />
|
||||||
|
<div class="mb-5 grid grid-cols-3 gap-4 py-2">
|
||||||
|
<Selectable
|
||||||
|
v-model="form.sizeUnit"
|
||||||
|
label="description"
|
||||||
|
title="Unidad de medida"
|
||||||
|
:options="sizeUnit"
|
||||||
|
multiple
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="form.costMin"
|
||||||
|
class="col-span-2"
|
||||||
|
id="Costo mínimo (UMAs)"
|
||||||
|
type="text"
|
||||||
|
:onError="form.errors.costMin"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="form.costMax"
|
||||||
|
class="col-span-2"
|
||||||
|
id="Costo máximo (UMAs)"
|
||||||
|
type="text"
|
||||||
|
:onError="form.errors.costMax"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 p-7 flex justify-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-1/4 bg-[#7a0b3a] hover:bg-[#68082e] text-white font-medium py-3.5 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Guardar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
212
src/components/App/DiscountSection.vue
Normal file
212
src/components/App/DiscountSection.vue
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import Input from "@Holos/Form/Input.vue";
|
||||||
|
|
||||||
|
/** Eventos */
|
||||||
|
const emit = defineEmits(['discount-authorized']);
|
||||||
|
|
||||||
|
/** Refs */
|
||||||
|
const searchForm = ref({
|
||||||
|
fineNumber: "",
|
||||||
|
placa: "",
|
||||||
|
curp: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
const discountData = ref({
|
||||||
|
fecha: "",
|
||||||
|
placa: "",
|
||||||
|
vin: "",
|
||||||
|
licencia: "",
|
||||||
|
tarjeta: "",
|
||||||
|
rfc: "",
|
||||||
|
nombre: "",
|
||||||
|
montoOriginal: "",
|
||||||
|
pagoMinimo: "",
|
||||||
|
nuevoMonto: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Métodos */
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (!searchForm.value.fineNumber && !searchForm.value.placa && !searchForm.value.curp) {
|
||||||
|
alert("Por favor ingresa al menos un criterio de búsqueda");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Buscando multa:", searchForm.value);
|
||||||
|
|
||||||
|
// Simular datos encontrados (temporal)
|
||||||
|
discountData.value = {
|
||||||
|
fecha: "2025-11-11",
|
||||||
|
placa: "ABC-123-DEF",
|
||||||
|
vin: "1HGBH41JXMN109186",
|
||||||
|
licencia: "LIC123456",
|
||||||
|
tarjeta: "TC987654",
|
||||||
|
rfc: "XAXX010101000",
|
||||||
|
nombre: "Juan Pérez García",
|
||||||
|
montoOriginal: "$1,500.00",
|
||||||
|
pagoMinimo: "$750.00",
|
||||||
|
nuevoMonto: ""
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAuthorize = () => {
|
||||||
|
if (!discountData.value.nuevoMonto) {
|
||||||
|
alert("Por favor ingresa el nuevo monto autorizado");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Autorizando descuento:", discountData.value);
|
||||||
|
emit('discount-authorized', discountData.value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<!-- Formulario de búsqueda -->
|
||||||
|
<div class="bg-white rounded-xl p-6 m-3 max-w-auto shadow-lg mb-6">
|
||||||
|
<h3 class="text-xl font-semibold mb-6 text-gray-800">
|
||||||
|
Buscar Multa para Autorización
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<form @submit.prevent="handleSearch">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-end">
|
||||||
|
<!-- Número de Multa -->
|
||||||
|
<Input
|
||||||
|
v-model="searchForm.fineNumber"
|
||||||
|
id="Número de Multa"
|
||||||
|
type="text"
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Placa -->
|
||||||
|
<Input
|
||||||
|
v-model="searchForm.placa"
|
||||||
|
id="Placa"
|
||||||
|
type="text"
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- CURP -->
|
||||||
|
<Input
|
||||||
|
v-model="searchForm.curp"
|
||||||
|
id="CURP"
|
||||||
|
type="text"
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Botón Buscar -->
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="py-3 px-8 rounded-lg transition-colors h-[42px] bg-[#7a0b3a] hover:bg-[#68082e] text-white font-medium"
|
||||||
|
>
|
||||||
|
Buscar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Detalles para Autorización -->
|
||||||
|
<div class="bg-white rounded-xl p-6 m-3 max-w-auto shadow-lg">
|
||||||
|
<h3 class="text-xl font-semibold mb-6 text-gray-800">
|
||||||
|
Detalles para Autorización
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<form @submit.prevent="handleAuthorize">
|
||||||
|
<!-- Fecha y Placa -->
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-5">
|
||||||
|
<Input
|
||||||
|
v-model="discountData.fecha"
|
||||||
|
id="Fecha de Multa"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="discountData.placa"
|
||||||
|
id="Placa"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- VIN y Licencia -->
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-5">
|
||||||
|
<Input
|
||||||
|
v-model="discountData.vin"
|
||||||
|
id="VIN"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="discountData.licencia"
|
||||||
|
id="Licencia"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tarjeta de Circulación y RFC -->
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-5">
|
||||||
|
<Input
|
||||||
|
v-model="discountData.tarjeta"
|
||||||
|
id="Tarjeta de Circulación"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="discountData.rfc"
|
||||||
|
id="RFC"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nombre -->
|
||||||
|
<div class="mb-5">
|
||||||
|
<Input
|
||||||
|
v-model="discountData.nombre"
|
||||||
|
id="Nombre"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Línea separadora -->
|
||||||
|
<hr class="my-6 border-gray-300" />
|
||||||
|
|
||||||
|
<!-- Monto Original y Pago Mínimo -->
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-5">
|
||||||
|
<Input
|
||||||
|
v-model="discountData.montoOriginal"
|
||||||
|
id="Monto a Pagar (Original)"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="discountData.pagoMinimo"
|
||||||
|
id="Pago Mínimo"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nuevo Monto a Cobrar (editable) -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<Input
|
||||||
|
v-model="discountData.nuevoMonto"
|
||||||
|
id="Nuevo Monto a Cobrar (Autorizado)"
|
||||||
|
type="text"
|
||||||
|
placeholder="Ingrese el nuevo monto autorizado"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Botón Autorizar -->
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full bg-green-700 hover:bg-green-600 text-white font-medium py-3.5 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Autorizar descuento
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
196
src/components/App/FineSection.vue
Normal file
196
src/components/App/FineSection.vue
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
import Input from "@Holos/Form/Input.vue";
|
||||||
|
|
||||||
|
/** Eventos */
|
||||||
|
const emit = defineEmits(["fine-searched"]);
|
||||||
|
|
||||||
|
/** Refs */
|
||||||
|
const fineNumber = ref("");
|
||||||
|
const fineData = ref({
|
||||||
|
fecha: "",
|
||||||
|
placa: "",
|
||||||
|
vin: "",
|
||||||
|
licencia: "",
|
||||||
|
tarjeta: "",
|
||||||
|
rfc: "",
|
||||||
|
nombre: "",
|
||||||
|
monto: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Métodos */
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (!fineNumber.value.trim()) {
|
||||||
|
alert("Por favor ingresa un número de multa");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aquí harías la llamada a la API para buscar la multa
|
||||||
|
console.log("Buscando multa:", fineNumber.value);
|
||||||
|
|
||||||
|
// Simular datos encontrados (temporal)
|
||||||
|
fineData.value = {
|
||||||
|
fecha: "2025-11-11",
|
||||||
|
placa: "ABC-123-DEF",
|
||||||
|
vin: "1HGBH41JXMN109186",
|
||||||
|
licencia: "LIC123456",
|
||||||
|
tarjeta: "TC987654",
|
||||||
|
rfc: "XAXX010101000",
|
||||||
|
nombre: "Juan Pérez García",
|
||||||
|
monto: "$1,500.00",
|
||||||
|
};
|
||||||
|
|
||||||
|
emit("fine-searched", fineData.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePayment = () => {
|
||||||
|
if (!fineData.value.monto) {
|
||||||
|
alert("No hay multa cargada para cobrar");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Procesando pago de:", fineData.value.monto);
|
||||||
|
emit("payment-processed", fineData.value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 p-6">
|
||||||
|
<div class="bg-white rounded-xl p-6 shadow-lg">
|
||||||
|
<h3 class="text-xl font-semibold mb-4 text-gray-800">Buscar Multa</h3>
|
||||||
|
|
||||||
|
<div class="mb-6">
|
||||||
|
<label class="block text-sm text-gray-600 font-medium mb-2">
|
||||||
|
Escanear Código QR
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="w-full h-64 bg-gray-800 rounded-lg flex flex-col items-center justify-center"
|
||||||
|
>
|
||||||
|
<!-- Icono de QR -->
|
||||||
|
<div class="text-gray-500 mb-3">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-16 w-16"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-500 text-sm">Simulador de cámara QR</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-5">
|
||||||
|
<Input
|
||||||
|
v-model="fineNumber"
|
||||||
|
id="Número de Multa"
|
||||||
|
type="text"
|
||||||
|
placeholder="Ingrese el número de multa"
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="handleSearch"
|
||||||
|
type="button"
|
||||||
|
class="w-full bg-[#7a0b3a] hover:bg-[#68082e] text-white font-medium py-3.5 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Buscar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white rounded-xl p-6 shadow-lg">
|
||||||
|
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
||||||
|
Detalles de la Multa
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<form @submit.prevent="handlePayment">
|
||||||
|
<!-- Fecha y Placa -->
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-5">
|
||||||
|
<Input
|
||||||
|
v-model="fineData.fecha"
|
||||||
|
id="Fecha de Multa"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="fineData.placa"
|
||||||
|
id="Placa"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- VIN y Licencia -->
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-5">
|
||||||
|
<Input
|
||||||
|
v-model="fineData.vin"
|
||||||
|
id="VIN"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="fineData.licencia"
|
||||||
|
id="Licencia"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tarjeta de Circulación y RFC -->
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-5">
|
||||||
|
<Input
|
||||||
|
v-model="fineData.tarjeta"
|
||||||
|
id="Tarjeta de Circulación"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
v-model="fineData.rfc"
|
||||||
|
id="RFC"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nombre -->
|
||||||
|
<div class="mb-5">
|
||||||
|
<Input
|
||||||
|
v-model="fineData.nombre"
|
||||||
|
id="Nombre"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Línea separadora -->
|
||||||
|
<hr class="my-6 border-gray-300" />
|
||||||
|
|
||||||
|
<!-- Monto a Pagar -->
|
||||||
|
<div class="mb-5">
|
||||||
|
<Input
|
||||||
|
v-model="fineData.monto"
|
||||||
|
id="Monto a Pagar"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
class="text-lg font-semibold"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Botón Cobrar -->
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full bg-[#7a0b3a] hover:bg-[#68082e] text-white font-medium py-3.5 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Cobrar
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
203
src/components/App/MembershipSection.vue
Normal file
203
src/components/App/MembershipSection.vue
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
import Input from "@Holos/Form/Input.vue";
|
||||||
|
import Selectable from "@Holos/Form/Selectable.vue";
|
||||||
|
|
||||||
|
/** Eventos */
|
||||||
|
const emit = defineEmits(["membership-paid"]);
|
||||||
|
|
||||||
|
/** Refs */
|
||||||
|
const searchForm = ref({
|
||||||
|
membershipNumber: "",
|
||||||
|
curp: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const memberData = ref({
|
||||||
|
image: null,
|
||||||
|
name: "",
|
||||||
|
curp: "",
|
||||||
|
tutorName: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const paymentData = ref({
|
||||||
|
service: [],
|
||||||
|
quantity: 1,
|
||||||
|
totalAmount: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const serviceOptions = ref([
|
||||||
|
{ id: 1, description: "Membresía Mensual" },
|
||||||
|
{ id: 2, description: "Membresía Trimestral" },
|
||||||
|
{ id: 3, description: "Membresía Semestral" },
|
||||||
|
{ id: 4, description: "Membresía Anual" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** Métodos */
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (!searchForm.value.membershipNumber && !searchForm.value.curp) {
|
||||||
|
alert("Por favor ingresa al menos un criterio de búsqueda");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Buscando miembro:", searchForm.value);
|
||||||
|
|
||||||
|
// Simular datos encontrados
|
||||||
|
memberData.value = {
|
||||||
|
name: "Juan Pérez García",
|
||||||
|
curp: "PEGJ900101HDFRNN09",
|
||||||
|
tutorName: "María López Hernández",
|
||||||
|
image: null,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePayment = () => {
|
||||||
|
if (!paymentData.value.service) {
|
||||||
|
alert("Por favor selecciona un servicio");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Procesando pago de membresía:", {
|
||||||
|
member: memberData.value,
|
||||||
|
payment: paymentData.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
emit("membership-paid", {
|
||||||
|
member: memberData.value,
|
||||||
|
payment: paymentData.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateTotal = () => {
|
||||||
|
paymentData.value.totalAmount = "0.00";
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<!-- Formulario de búsqueda -->
|
||||||
|
<div class="bg-white rounded-xl p-6 m-3 max-w-auto shadow-lg mb-6">
|
||||||
|
<h3 class="text-xl font-semibold mb-6 text-gray-800">Buscar miembro</h3>
|
||||||
|
<form @submit.prevent="handleSearch">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-end">
|
||||||
|
<!-- Número de Membresía -->
|
||||||
|
<Input
|
||||||
|
v-model="searchForm.membershipNumber"
|
||||||
|
id="Número de Membresía"
|
||||||
|
type="text"
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- CURP -->
|
||||||
|
<Input
|
||||||
|
v-model="searchForm.curp"
|
||||||
|
id="CURP"
|
||||||
|
type="text"
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Botón Buscar -->
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="py-3 px-8 rounded-lg transition-colors h-[42px] bg-[#7a0b3a] hover:bg-[#68082e] text-white font-medium"
|
||||||
|
>
|
||||||
|
Buscar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Información del Miembro -->
|
||||||
|
<div class="bg-white rounded-xl p-6 m-3 max-w-auto shadow-lg mb-6">
|
||||||
|
<h3 class="text-xl font-semibold mb-6 text-gray-800">
|
||||||
|
Información del Miembro
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<!-- Fotografía -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm text-gray-600 font-medium mb-2">
|
||||||
|
Fotografía
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="w-full h-64 bg-gray-200 rounded-lg flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-24 w-24 text-gray-400"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Datos del miembro -->
|
||||||
|
<div class="space-y-5">
|
||||||
|
<Input v-model="memberData.name" id="Nombre" type="text" disabled />
|
||||||
|
|
||||||
|
<Input v-model="memberData.curp" id="CURP" type="text" disabled />
|
||||||
|
|
||||||
|
<Input
|
||||||
|
v-model="memberData.tutorName"
|
||||||
|
id="Nombre Tutor"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Realizar Cobro de Membresía -->
|
||||||
|
<div class="bg-white rounded-xl p-6 m-3 max-w-auto shadow-lg">
|
||||||
|
<h3 class="text-xl font-semibold mb-6 text-gray-800">
|
||||||
|
Realizar Cobro de Membresía
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<form @submit.prevent="handlePayment">
|
||||||
|
<div class="grid grid-cols-3 gap-4 mb-6">
|
||||||
|
<!-- Servicio a Pagar -->
|
||||||
|
<Selectable
|
||||||
|
v-model="paymentData.service"
|
||||||
|
label="description"
|
||||||
|
title="Servicio a pagar"
|
||||||
|
:options="serviceOptions"
|
||||||
|
multiple
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Cantidad -->
|
||||||
|
<Input
|
||||||
|
v-model="paymentData.quantity"
|
||||||
|
id="Cantidad (días)"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
@input="calculateTotal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Monto Total -->
|
||||||
|
<Input
|
||||||
|
v-model="paymentData.totalAmount"
|
||||||
|
id="Monto Total"
|
||||||
|
type="text"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Botón Cobrar -->
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full bg-green-700 hover:bg-green-600 text-white font-medium py-3.5 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Pagar membresía
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -80,6 +80,25 @@ export default {
|
|||||||
description: 'Historial de acciones realizadas por los usuarios en orden cronológico.'
|
description: 'Historial de acciones realizadas por los usuarios en orden cronológico.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
address: {
|
||||||
|
title: 'Direcciones',
|
||||||
|
create: {
|
||||||
|
title: 'Crear dirección',
|
||||||
|
description: 'Permite crear nuevas direcciones.',
|
||||||
|
onSuccess: 'Dirección creada exitosamente',
|
||||||
|
onError: 'Error al crear la dirección'
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
title: 'Editar dirección',
|
||||||
|
description: 'Permite editar direcciones existentes.',
|
||||||
|
onSuccess: 'Dirección actualizada exitosamente',
|
||||||
|
onError: 'Error al actualizar la dirección'
|
||||||
|
},
|
||||||
|
deleted: 'Dirección eliminada',
|
||||||
|
permissions: {
|
||||||
|
title: 'Permisos de dirección'
|
||||||
|
}
|
||||||
|
},
|
||||||
app: {
|
app: {
|
||||||
theme: {
|
theme: {
|
||||||
dark: 'Tema oscuro',
|
dark: 'Tema oscuro',
|
||||||
|
|||||||
@ -36,9 +36,29 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
<Link
|
<Link
|
||||||
icon="add_home"
|
icon="add_home"
|
||||||
name="Crear Dirección"
|
name="Gestionar Direcciones"
|
||||||
to="address.index"
|
to="address.index"
|
||||||
/>
|
/>
|
||||||
|
<Link
|
||||||
|
icon="how_to_vote"
|
||||||
|
name="Gestionar Conceptos"
|
||||||
|
to="concept.index"
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
icon="receipt_long"
|
||||||
|
name="Gestionar Multas"
|
||||||
|
to="fine.index"
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
icon="car_tag"
|
||||||
|
name="Autorizar Descuentos"
|
||||||
|
to="discount.index"
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
icon="card_membership"
|
||||||
|
name="Cobro de Membresía"
|
||||||
|
to="membership.index"
|
||||||
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
<Section
|
<Section
|
||||||
v-if="hasPermission('users.index')"
|
v-if="hasPermission('users.index')"
|
||||||
|
|||||||
@ -21,11 +21,12 @@ const form = useForm({
|
|||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
password: '',
|
password: '',
|
||||||
roles: []
|
roles: [],
|
||||||
|
address: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
const roles = ref([]);
|
const roles = ref([]);
|
||||||
|
const addresses = ref([]);
|
||||||
/** Métodos */
|
/** Métodos */
|
||||||
function submit() {
|
function submit() {
|
||||||
form.transform(data => ({
|
form.transform(data => ({
|
||||||
@ -80,5 +81,12 @@ onMounted(() => {
|
|||||||
:options="roles"
|
:options="roles"
|
||||||
multiple
|
multiple
|
||||||
/>
|
/>
|
||||||
|
<Selectable
|
||||||
|
v-model="form.address"
|
||||||
|
label="description"
|
||||||
|
title="Direcciones"
|
||||||
|
:options="addresses"
|
||||||
|
multiple
|
||||||
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,9 +1,136 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import PageHeader from '@Holos/PageHeader.vue';
|
import { onMounted, ref } from "vue";
|
||||||
|
import { RouterLink } from "vue-router";
|
||||||
|
import { useSearcher } from "@Services/Api";
|
||||||
|
import { can, apiTo, viewTo, transl } from "./Module";
|
||||||
|
|
||||||
|
import Table from "@Holos/Table.vue";
|
||||||
|
import IconButton from "@Holos/Button/Icon.vue";
|
||||||
|
import PageHeader from "@Holos/PageHeader.vue";
|
||||||
|
import AddressForm from "@App/AddressSection.vue";
|
||||||
|
import DestroyView from '@Holos/Modal/Template/Destroy.vue';
|
||||||
|
import ModalController from "@Controllers/ModalController.js";
|
||||||
|
|
||||||
|
/** Controladores */
|
||||||
|
const Modal = new ModalController();
|
||||||
|
|
||||||
|
/** Propiedades */
|
||||||
|
const destroyModal = ref(Modal.destroyModal);
|
||||||
|
const editModal = ref(Modal.editModal);
|
||||||
|
const modelModal = ref(Modal.modelModal);
|
||||||
|
|
||||||
|
/** Inicializar searcher */
|
||||||
|
//const models = ref(searcher.models);
|
||||||
|
const models = ref({
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
const processing = ref(false);
|
||||||
|
//const searcher = useSearcher(apiTo("index"));
|
||||||
|
const searcher = {
|
||||||
|
models,
|
||||||
|
processing,
|
||||||
|
pagination: () => {},
|
||||||
|
search: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Cargar datos iniciales */
|
||||||
|
onMounted(() => {
|
||||||
|
searcher.search();
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Manejar creación de nueva dirección */
|
||||||
|
const handleAddressCreated = async (address) => {
|
||||||
|
try {
|
||||||
|
// Aquí harías la llamada a la API para crear la dirección
|
||||||
|
// const response = await axios.post(apiTo('store'), address);
|
||||||
|
// Recargar la tabla desde la API
|
||||||
|
await searcher.search();
|
||||||
|
|
||||||
|
// O agregar localmente si no tienes API aún:
|
||||||
|
if (!models.value.data) {
|
||||||
|
models.value.data = [];
|
||||||
|
}
|
||||||
|
models.value.data.unshift({
|
||||||
|
id: Date.now(),
|
||||||
|
description: address.description,
|
||||||
|
});
|
||||||
|
models.value.total = models.value.data.length;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al crear dirección:", error);
|
||||||
|
alert("Error al crear la dirección");
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PageHeader title="Dashboard" />
|
<PageHeader title="Creación de Dirección" />
|
||||||
<p><b>{{ $t('welcome') }}</b>, {{ $page.user.name }}.</p>
|
<AddressForm @address-created="handleAddressCreated" />
|
||||||
</template>
|
<div class="m-3">
|
||||||
|
<Table
|
||||||
|
:items="models"
|
||||||
|
:processing="searcher.processing.value"
|
||||||
|
@send-pagination="(page) => searcher.pagination(page)"
|
||||||
|
>
|
||||||
|
<template #head>
|
||||||
|
<th v-text="$t('name')" />
|
||||||
|
<th v-text="$t('actions')" class="w-32 text-center" />
|
||||||
|
</template>
|
||||||
|
<template #body="{ items }">
|
||||||
|
<tr v-for="model in items" :key="model.id" class="table-row">
|
||||||
|
<td class="table-cell border">
|
||||||
|
{{ model.description }}
|
||||||
|
</td>
|
||||||
|
<td class="table-cell">
|
||||||
|
<div class="table-actions">
|
||||||
|
<IconButton
|
||||||
|
v-if="can('edit') && ![1, 2].includes(model.id)"
|
||||||
|
icon="license"
|
||||||
|
:title="transl('permissions.title')"
|
||||||
|
@click="Modal.switchEditModal(model)"
|
||||||
|
outline
|
||||||
|
/>
|
||||||
|
<RouterLink
|
||||||
|
v-if="can('edit')"
|
||||||
|
class="h-fit"
|
||||||
|
:to="viewTo({ name: 'edit', params: { id: model.id } })"
|
||||||
|
>
|
||||||
|
<IconButton icon="edit" :title="$t('crud.edit')" outline />
|
||||||
|
</RouterLink>
|
||||||
|
<IconButton
|
||||||
|
v-if="can('destroy') && ![1, 2].includes(model.id)"
|
||||||
|
icon="delete"
|
||||||
|
:title="$t('crud.destroy')"
|
||||||
|
@click="Modal.switchDestroyModal(model)"
|
||||||
|
outline
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
<template #empty>
|
||||||
|
<td colspan="2" class="table-cell text-center py-4 text-gray-500">
|
||||||
|
No hay direcciones registradas
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
<DestroyView
|
||||||
|
v-if="can('destroy')"
|
||||||
|
title="description"
|
||||||
|
subtitle=""
|
||||||
|
:model="modelModal"
|
||||||
|
:show="destroyModal"
|
||||||
|
:to="(address) => apiTo('destroy', { address: address.id })"
|
||||||
|
@close="Modal.switchDestroyModal"
|
||||||
|
@update="
|
||||||
|
() => {
|
||||||
|
const index = models.data.findIndex((m) => m.id === modelModal.id);
|
||||||
|
if (index > -1) {
|
||||||
|
models.data.splice(index, 1);
|
||||||
|
models.total--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|||||||
21
src/pages/App/Address/Module.js
Normal file
21
src/pages/App/Address/Module.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { lang } from '@Lang/i18n';
|
||||||
|
import { hasPermission } from '@Plugins/RolePermission.js';
|
||||||
|
|
||||||
|
// Ruta API
|
||||||
|
const apiTo = (name, params = {}) => route(`address.${name}`, params)
|
||||||
|
|
||||||
|
// Ruta visual
|
||||||
|
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `address.${name}`, params, query })
|
||||||
|
|
||||||
|
// Obtener traducción del componente
|
||||||
|
const transl = (str) => lang(`address.${str}`)
|
||||||
|
|
||||||
|
// Determina si un usuario puede hacer algo no en base a los permisos
|
||||||
|
const can = (permission) => hasPermission(`address.${permission}`)
|
||||||
|
|
||||||
|
export {
|
||||||
|
can,
|
||||||
|
viewTo,
|
||||||
|
apiTo,
|
||||||
|
transl
|
||||||
|
}
|
||||||
117
src/pages/App/Concept/Index.vue
Normal file
117
src/pages/App/Concept/Index.vue
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
import { RouterLink } from "vue-router";
|
||||||
|
import { useSearcher } from "@Services/Api";
|
||||||
|
import { can, apiTo, viewTo, transl } from "./Module";
|
||||||
|
|
||||||
|
import Table from "@Holos/Table.vue";
|
||||||
|
import IconButton from "@Holos/Button/Icon.vue";
|
||||||
|
import PageHeader from "@Holos/PageHeader.vue";
|
||||||
|
import ConceptForm from "@App/ConceptSection.vue";
|
||||||
|
import DestroyView from "@Holos/Modal/Template/Destroy.vue";
|
||||||
|
import ModalController from "@Controllers/ModalController.js";
|
||||||
|
|
||||||
|
/** Controladores */
|
||||||
|
const Modal = new ModalController();
|
||||||
|
|
||||||
|
/** Propiedades */
|
||||||
|
const destroyModal = ref(Modal.destroyModal);
|
||||||
|
const editModal = ref(Modal.editModal);
|
||||||
|
const modelModal = ref(Modal.modelModal);
|
||||||
|
|
||||||
|
/** Inicializar searcher */
|
||||||
|
//const models = ref(searcher.models);
|
||||||
|
const models = ref({
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
const processing = ref(false);
|
||||||
|
//const searcher = useSearcher(apiTo("index"));
|
||||||
|
const searcher = {
|
||||||
|
models,
|
||||||
|
processing,
|
||||||
|
pagination: () => {},
|
||||||
|
search: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Cargar datos iniciales */
|
||||||
|
onMounted(() => {
|
||||||
|
searcher.search();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<PageHeader title="Creación de Concepto" />
|
||||||
|
<ConceptForm />
|
||||||
|
<div class="m-3">
|
||||||
|
<Table
|
||||||
|
:items="models"
|
||||||
|
:processing="searcher.processing.value"
|
||||||
|
@send-pagination="(page) => searcher.pagination(page)"
|
||||||
|
>
|
||||||
|
<template #head>
|
||||||
|
<th v-text="'Dirección'" />
|
||||||
|
<th v-text="'Nombre corto'" />
|
||||||
|
<th v-text="'Nombre'" />
|
||||||
|
<th v-text="'Instrumento Legal'" />
|
||||||
|
<th v-text="'Articulado'" />
|
||||||
|
<th v-text="'Descripción'" />
|
||||||
|
<th v-text="$t('actions')" class="w-32 text-center" />
|
||||||
|
</template>
|
||||||
|
<template #body="{ items }">
|
||||||
|
<tr v-for="model in items" :key="model.id" class="table-row">
|
||||||
|
<td class="table-cell border">
|
||||||
|
{{ model.description }}
|
||||||
|
</td>
|
||||||
|
<td class="table-cell">
|
||||||
|
<div class="table-actions">
|
||||||
|
<IconButton
|
||||||
|
v-if="can('edit') && ![1, 2].includes(model.id)"
|
||||||
|
icon="license"
|
||||||
|
:title="transl('permissions.title')"
|
||||||
|
@click="Modal.switchEditModal(model)"
|
||||||
|
outline
|
||||||
|
/>
|
||||||
|
<RouterLink
|
||||||
|
v-if="can('edit')"
|
||||||
|
class="h-fit"
|
||||||
|
:to="viewTo({ name: 'edit', params: { id: model.id } })"
|
||||||
|
>
|
||||||
|
<IconButton icon="edit" :title="$t('crud.edit')" outline />
|
||||||
|
</RouterLink>
|
||||||
|
<IconButton
|
||||||
|
v-if="can('destroy') && ![1, 2].includes(model.id)"
|
||||||
|
icon="delete"
|
||||||
|
:title="$t('crud.destroy')"
|
||||||
|
@click="Modal.switchDestroyModal(model)"
|
||||||
|
outline
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
<template #empty>
|
||||||
|
<td colspan="2" class="table-cell text-center py-4 text-gray-500">
|
||||||
|
No hay direcciones registradas
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
<DestroyView
|
||||||
|
v-if="can('destroy')"
|
||||||
|
title="description"
|
||||||
|
subtitle=""
|
||||||
|
:model="modelModal"
|
||||||
|
:show="destroyModal"
|
||||||
|
:to="(address) => apiTo('destroy', { address: address.id })"
|
||||||
|
@close="Modal.switchDestroyModal"
|
||||||
|
@update="
|
||||||
|
() => {
|
||||||
|
const index = models.data.findIndex((m) => m.id === modelModal.id);
|
||||||
|
if (index > -1) {
|
||||||
|
models.data.splice(index, 1);
|
||||||
|
models.total--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
21
src/pages/App/Concept/Module.js
Normal file
21
src/pages/App/Concept/Module.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { lang } from '@Lang/i18n';
|
||||||
|
import { hasPermission } from '@Plugins/RolePermission.js';
|
||||||
|
|
||||||
|
// Ruta API
|
||||||
|
const apiTo = (name, params = {}) => route(`concept.${name}`, params)
|
||||||
|
|
||||||
|
// Ruta visual
|
||||||
|
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `concept.${name}`, params, query })
|
||||||
|
|
||||||
|
// Obtener traducción del componente
|
||||||
|
const transl = (str) => lang(`concept.${str}`)
|
||||||
|
|
||||||
|
// Determina si un usuario puede hacer algo no en base a los permisos
|
||||||
|
const can = (permission) => hasPermission(`concept.${permission}`)
|
||||||
|
|
||||||
|
export {
|
||||||
|
can,
|
||||||
|
viewTo,
|
||||||
|
apiTo,
|
||||||
|
transl
|
||||||
|
}
|
||||||
9
src/pages/App/Discount/Index.vue
Normal file
9
src/pages/App/Discount/Index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup>
|
||||||
|
import PageHeader from "@Holos/PageHeader.vue";
|
||||||
|
import Discount from '@App/DiscountSection.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageHeader title="Autorizar Descuentos" />
|
||||||
|
<Discount />
|
||||||
|
</template>
|
||||||
9
src/pages/App/Fine/Index.vue
Normal file
9
src/pages/App/Fine/Index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup>
|
||||||
|
import PageHeader from "@Holos/PageHeader.vue";
|
||||||
|
import FineSection from '@App/FineSection.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageHeader title="Cobro de Multa" />
|
||||||
|
<FineSection />
|
||||||
|
</template>
|
||||||
9
src/pages/App/Membership/Index.vue
Normal file
9
src/pages/App/Membership/Index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup>
|
||||||
|
import PageHeader from '@Holos/PageHeader.vue';
|
||||||
|
import MembershipSection from '@App/MembershipSection.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageHeader title="Cobro de Membresía" />
|
||||||
|
<MembershipSection />
|
||||||
|
</template>
|
||||||
@ -57,6 +57,46 @@ const router = createRouter({
|
|||||||
component: () => import('@Pages/App/Address/Index.vue')
|
component: () => import('@Pages/App/Address/Index.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'concept',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'concept.index',
|
||||||
|
component: () => import('@Pages/App/Concept/Index.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'fine',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'fine.index',
|
||||||
|
component: () => import('@Pages/App/Fine/Index.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'discount',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'discount.index',
|
||||||
|
component: () => import('@Pages/App/Discount/Index.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'membership',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'membership.index',
|
||||||
|
component: () => import('@Pages/App/Membership/Index.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -16,6 +16,7 @@ export default defineConfig({
|
|||||||
'@Components': fileURLToPath(new URL('./src/components', import.meta.url)),
|
'@Components': fileURLToPath(new URL('./src/components', import.meta.url)),
|
||||||
'@Controllers': fileURLToPath(new URL('./src/controllers', import.meta.url)),
|
'@Controllers': fileURLToPath(new URL('./src/controllers', import.meta.url)),
|
||||||
'@Holos': fileURLToPath(new URL('./src/components/Holos', import.meta.url)),
|
'@Holos': fileURLToPath(new URL('./src/components/Holos', import.meta.url)),
|
||||||
|
'@App': fileURLToPath(new URL('./src/components/App', import.meta.url)),
|
||||||
'@Layouts': fileURLToPath(new URL('./src/layouts', import.meta.url)),
|
'@Layouts': fileURLToPath(new URL('./src/layouts', import.meta.url)),
|
||||||
'@Lang': fileURLToPath(new URL('./src/lang', import.meta.url)),
|
'@Lang': fileURLToPath(new URL('./src/lang', import.meta.url)),
|
||||||
'@Router': fileURLToPath(new URL('./src/router', import.meta.url)),
|
'@Router': fileURLToPath(new URL('./src/router', import.meta.url)),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user