Asignacion de presupuesto
This commit is contained in:
parent
8dcfa647b0
commit
a24218187b
@ -86,6 +86,9 @@ export default {
|
|||||||
light: 'Tema claro'
|
light: 'Tema claro'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
approve: 'Aprobar',
|
||||||
|
assigned_budget: 'Presupuesto asignado',
|
||||||
|
available_budget: 'Presupuesto disponible',
|
||||||
assistances: {
|
assistances: {
|
||||||
create: {
|
create: {
|
||||||
title: 'Generar asistencia',
|
title: 'Generar asistencia',
|
||||||
@ -137,6 +140,7 @@ export default {
|
|||||||
certification_type: 'Tipo de certificación',
|
certification_type: 'Tipo de certificación',
|
||||||
clear: 'Limpiar',
|
clear: 'Limpiar',
|
||||||
close:"Cerrar",
|
close:"Cerrar",
|
||||||
|
company: 'Empresa',
|
||||||
confirm:'Confirmar',
|
confirm:'Confirmar',
|
||||||
copyright:'Todos los derechos reservados.',
|
copyright:'Todos los derechos reservados.',
|
||||||
contact:'Contacto',
|
contact:'Contacto',
|
||||||
@ -250,6 +254,18 @@ export default {
|
|||||||
passwordCurrent:'Contraseña actual',
|
passwordCurrent:'Contraseña actual',
|
||||||
passwordReset:'Restaurar contraseña',
|
passwordReset:'Restaurar contraseña',
|
||||||
paternal:'Apellido paterno',
|
paternal:'Apellido paterno',
|
||||||
|
petty_cash: {
|
||||||
|
title: 'Caja Chica',
|
||||||
|
description: 'Gestión de caja chica para gastos menores'
|
||||||
|
},
|
||||||
|
pettyCashes: {
|
||||||
|
create: {
|
||||||
|
description: 'Crea una nueva caja chica para gestión de gastos menores'
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
description: 'Modifica los datos de la caja chica seleccionada'
|
||||||
|
}
|
||||||
|
},
|
||||||
phone:'Teléfono',
|
phone:'Teléfono',
|
||||||
photo: {
|
photo: {
|
||||||
new: 'Seleccionar una nueva foto',
|
new: 'Seleccionar una nueva foto',
|
||||||
|
|||||||
@ -97,10 +97,15 @@ onMounted(() => {
|
|||||||
to="admin.events.index"
|
to="admin.events.index"
|
||||||
:collapsed="true"
|
:collapsed="true"
|
||||||
>
|
>
|
||||||
|
<Link
|
||||||
|
icon="grid_view"
|
||||||
|
name="Cajas Chicas"
|
||||||
|
to="admin.events.pettyCashes"
|
||||||
|
/>
|
||||||
<Link
|
<Link
|
||||||
icon="grid_view"
|
icon="grid_view"
|
||||||
name="Asignación de presupuesto"
|
name="Asignación de presupuesto"
|
||||||
to="admin.events.assignamment"
|
to="admin.events.pettyCashes.assignamment"
|
||||||
/>
|
/>
|
||||||
<Link
|
<Link
|
||||||
icon="grid_view"
|
icon="grid_view"
|
||||||
|
|||||||
210
src/pages/Admin/Events/PettyCashes/Assignamment.vue
Normal file
210
src/pages/Admin/Events/PettyCashes/Assignamment.vue
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { api, useForm } from '@Services/Api';
|
||||||
|
import { apiTo, viewTo } from './Module';
|
||||||
|
|
||||||
|
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||||
|
import PageHeader from '@Holos/PageHeader.vue';
|
||||||
|
import IconButton from '@Holos/Button/Icon.vue';
|
||||||
|
import Input from '@Holos/Form/Input.vue';
|
||||||
|
import Selectable from '@Holos/Form/Selectable.vue';
|
||||||
|
import PrimaryButton from '@Holos/Button/Primary.vue';
|
||||||
|
|
||||||
|
/** Definiciones */
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
/** Propiedades */
|
||||||
|
const form = useForm({
|
||||||
|
petty_cash_id: '',
|
||||||
|
budget_items: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
concept: '',
|
||||||
|
amount: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const pettyCashes = ref([]);
|
||||||
|
let nextId = 2;
|
||||||
|
|
||||||
|
// Computed para calcular el total
|
||||||
|
const totalBudget = computed(() => {
|
||||||
|
return form.budget_items.reduce((total, item) => {
|
||||||
|
return total + (parseFloat(item.amount) || 0);
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Función para agregar nueva fila
|
||||||
|
const addBudgetItem = () => {
|
||||||
|
form.budget_items.push({
|
||||||
|
id: nextId++,
|
||||||
|
concept: '',
|
||||||
|
amount: 0
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Función para eliminar fila
|
||||||
|
const removeBudgetItem = (id) => {
|
||||||
|
if (form.budget_items.length > 1) {
|
||||||
|
form.budget_items = form.budget_items.filter(item => item.id !== id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Función para guardar presupuesto
|
||||||
|
function submit() {
|
||||||
|
// Filtrar elementos vacíos
|
||||||
|
const validItems = form.budget_items.filter(item =>
|
||||||
|
item.concept.trim() !== '' && item.amount > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (validItems.length === 0) {
|
||||||
|
Notify.error('Por favor, agrega al menos un elemento al presupuesto');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.transform(data => ({
|
||||||
|
...data,
|
||||||
|
petty_cash_id: form.petty_cash_id?.id,
|
||||||
|
budget_items: validItems
|
||||||
|
})).post(apiTo('store-budget', { pettyCash: form.petty_cash_id }), {
|
||||||
|
onSuccess: () => {
|
||||||
|
Notify.success('Presupuesto guardado correctamente');
|
||||||
|
router.push(viewTo({ name: 'index' }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Ciclos */
|
||||||
|
onMounted(() => {
|
||||||
|
api.catalog({
|
||||||
|
'pettyCash:all': null
|
||||||
|
}, {
|
||||||
|
onSuccess: (r) => pettyCashes.value = r['pettyCash:all'] ?? []
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageHeader
|
||||||
|
title="Asignación de Presupuesto"
|
||||||
|
>
|
||||||
|
<RouterLink :to="viewTo({ name: 'index' })">
|
||||||
|
<IconButton
|
||||||
|
class="text-white"
|
||||||
|
icon="arrow_back"
|
||||||
|
:title="$t('return')"
|
||||||
|
filled
|
||||||
|
/>
|
||||||
|
</RouterLink>
|
||||||
|
</PageHeader>
|
||||||
|
|
||||||
|
<div class="w-full pb-2">
|
||||||
|
<p class="text-justify text-sm">Gestiona y asigna el presupuesto para una caja chica</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Card principal -->
|
||||||
|
<section class="mt-6 bg-white rounded-lg shadow-sm p-6 dark:bg-primary-d dark:border-primary/20 dark:text-primary-dt">
|
||||||
|
|
||||||
|
<!-- Formulario de presupuesto -->
|
||||||
|
<form @submit.prevent="submit" class="space-y-6">
|
||||||
|
|
||||||
|
<!-- Campos principales -->
|
||||||
|
<div class="grid grid-cols-1 gap-6">
|
||||||
|
<Selectable
|
||||||
|
v-model="form.petty_cash_id"
|
||||||
|
id="petty_cash_id"
|
||||||
|
title="Caja Chica"
|
||||||
|
:onError="form.errors.petty_cash_id"
|
||||||
|
:options="pettyCashes"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filas de presupuesto -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 dark:text-primary-dt">Elementos del Presupuesto</h3>
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in form.budget_items"
|
||||||
|
:key="item.id"
|
||||||
|
class="grid grid-cols-1 md:grid-cols-12 gap-4 items-end"
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- Campo Concepto -->
|
||||||
|
<div class="md:col-span-5">
|
||||||
|
<Input
|
||||||
|
v-model="item.concept"
|
||||||
|
:id="`concept_${item.id}`"
|
||||||
|
title="Concepto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Campo Monto -->
|
||||||
|
<div class="md:col-span-4">
|
||||||
|
<Input
|
||||||
|
v-model.number="item.amount"
|
||||||
|
:id="`amount_${item.id}`"
|
||||||
|
title="Monto"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Botón de eliminar -->
|
||||||
|
<div class="md:col-span-3 flex justify-end">
|
||||||
|
<IconButton
|
||||||
|
v-if="form.budget_items.length > 1"
|
||||||
|
@click="removeBudgetItem(item.id)"
|
||||||
|
icon="delete"
|
||||||
|
class="text-red-500 hover:text-red-700"
|
||||||
|
:title="$t('delete')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Resumen del presupuesto -->
|
||||||
|
<div class="mt-8 p-4 bg-gray-50 rounded-lg dark:bg-primary/5">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<GoogleIcon class="text-gray-600 dark:text-primary-dt text-xl" name="account_balance_wallet" />
|
||||||
|
<span class="text-lg font-medium text-gray-800 dark:text-primary-dt">Total del Presupuesto:</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-bold text-[#2563eb] dark:text-primary-dt">
|
||||||
|
${{ totalBudget.toFixed(2) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Botones de acción -->
|
||||||
|
<div class="flex items-center justify-between pt-6 border-t border-gray-100 dark:border-primary/20">
|
||||||
|
|
||||||
|
<!-- Botón Agregar más -->
|
||||||
|
<IconButton
|
||||||
|
@click="addBudgetItem"
|
||||||
|
icon="add"
|
||||||
|
class="bg-green-600 hover:bg-green-700 text-white"
|
||||||
|
:title="$t('add')"
|
||||||
|
>
|
||||||
|
+ Agregar más
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<!-- Botón Guardar Presupuesto -->
|
||||||
|
<PrimaryButton
|
||||||
|
type="submit"
|
||||||
|
:processing="form.processing"
|
||||||
|
>
|
||||||
|
<GoogleIcon class="text-white text-xl mr-2" name="save" />
|
||||||
|
Guardar Presupuesto
|
||||||
|
</PrimaryButton>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
65
src/pages/Admin/Events/PettyCashes/Create.vue
Normal file
65
src/pages/Admin/Events/PettyCashes/Create.vue
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { api, useForm } from '@Services/Api';
|
||||||
|
import { apiTo, viewTo, transl } from './Module';
|
||||||
|
|
||||||
|
import PageHeader from '@Holos/PageHeader.vue';
|
||||||
|
import IconButton from '@Holos/Button/Icon.vue';
|
||||||
|
import Form from './Form.vue';
|
||||||
|
|
||||||
|
/** Definiciones */
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
/** Propiedades */
|
||||||
|
const form = useForm({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
company_id: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const companies = ref([]);
|
||||||
|
|
||||||
|
/** Métodos */
|
||||||
|
function submit() {
|
||||||
|
form.transform(data => ({
|
||||||
|
...data,
|
||||||
|
company_id: form.company_id?.id
|
||||||
|
})).post(apiTo('store'), {
|
||||||
|
onSuccess: () => {
|
||||||
|
Notify.success(Lang('register.create.onSuccess'))
|
||||||
|
router.push(viewTo({ name: 'index' }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ciclos */
|
||||||
|
onMounted(() => {
|
||||||
|
api.catalog({
|
||||||
|
'company:all': null
|
||||||
|
}, {
|
||||||
|
onSuccess: (r) => companies.value = r['company:all'] ?? []
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageHeader
|
||||||
|
:title="transl('create.title')"
|
||||||
|
>
|
||||||
|
<RouterLink :to="viewTo({ name: 'index' })">
|
||||||
|
<IconButton
|
||||||
|
class="text-white"
|
||||||
|
icon="arrow_back"
|
||||||
|
:title="$t('return')"
|
||||||
|
filled
|
||||||
|
/>
|
||||||
|
</RouterLink>
|
||||||
|
</PageHeader>
|
||||||
|
<Form
|
||||||
|
action="create"
|
||||||
|
:form="form"
|
||||||
|
:companies="companies"
|
||||||
|
@submit="submit"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
73
src/pages/Admin/Events/PettyCashes/Edit.vue
Normal file
73
src/pages/Admin/Events/PettyCashes/Edit.vue
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
||||||
|
import { api, useForm } from '@Services/Api';
|
||||||
|
import { viewTo, apiTo, transl } from './Module';
|
||||||
|
|
||||||
|
import IconButton from '@Holos/Button/Icon.vue'
|
||||||
|
import PageHeader from '@Holos/PageHeader.vue';
|
||||||
|
import Form from './Form.vue'
|
||||||
|
|
||||||
|
/** Definiciones */
|
||||||
|
const vroute = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
/** Propiedades */
|
||||||
|
const form = useForm({
|
||||||
|
id: null,
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
company_id: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const companies = ref([]);
|
||||||
|
|
||||||
|
/** Métodos */
|
||||||
|
function submit() {
|
||||||
|
form.transform(data => ({
|
||||||
|
...data,
|
||||||
|
company_id: data.company_id?.id
|
||||||
|
})).put(apiTo('update', { petty_cash: form.id }), {
|
||||||
|
onSuccess: () => {
|
||||||
|
Notify.success(Lang('register.edit.onSuccess'))
|
||||||
|
router.push(viewTo({ name: 'index' }));
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
api.get(apiTo('show', { petty_cash: vroute.params.id }), {
|
||||||
|
onSuccess: (r) => {
|
||||||
|
form.fill(r.petty_cash)
|
||||||
|
form.company_id = r.petty_cash.company
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
api.catalog({
|
||||||
|
'company:all': null,
|
||||||
|
}, {
|
||||||
|
onSuccess: (r) => {
|
||||||
|
companies.value = r['company:all'] ?? [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageHeader :title="transl('edit.title')">
|
||||||
|
<RouterLink :to="viewTo({ name: 'index' })">
|
||||||
|
<IconButton
|
||||||
|
class="text-white"
|
||||||
|
icon="arrow_back"
|
||||||
|
:title="$t('return')"
|
||||||
|
filled
|
||||||
|
/>
|
||||||
|
</RouterLink>
|
||||||
|
</PageHeader>
|
||||||
|
<Form
|
||||||
|
action="edit"
|
||||||
|
:form="form"
|
||||||
|
:companies="companies"
|
||||||
|
@submit="submit"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
74
src/pages/Admin/Events/PettyCashes/Form.vue
Normal file
74
src/pages/Admin/Events/PettyCashes/Form.vue
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<script setup>
|
||||||
|
import { transl } from './Module';
|
||||||
|
|
||||||
|
import PrimaryButton from '@Holos/Button/Primary.vue';
|
||||||
|
import Input from '@Holos/Form/Input.vue';
|
||||||
|
import Textarea from '@Holos/Form/Textarea.vue';
|
||||||
|
import Selectable from '@Holos/Form/Selectable.vue';
|
||||||
|
|
||||||
|
/** Eventos */
|
||||||
|
const emit = defineEmits([
|
||||||
|
'submit'
|
||||||
|
])
|
||||||
|
|
||||||
|
/** Propiedades */
|
||||||
|
defineProps({
|
||||||
|
action: {
|
||||||
|
default: 'create',
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
form: Object,
|
||||||
|
companies: Array
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Métodos */
|
||||||
|
function submit() {
|
||||||
|
emit('submit')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full pb-2">
|
||||||
|
<p class="text-justify text-sm" v-text="transl(`${action}.description`)" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<form @submit.prevent="submit" class="space-y-6">
|
||||||
|
|
||||||
|
<!-- Campos del formulario -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<Input
|
||||||
|
v-model="form.name"
|
||||||
|
id="name"
|
||||||
|
title="name"
|
||||||
|
:onError="form.errors.name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Selectable
|
||||||
|
v-model="form.company_id"
|
||||||
|
id="company_id"
|
||||||
|
title="company"
|
||||||
|
:onError="form.errors.company_id"
|
||||||
|
:options="companies"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Textarea
|
||||||
|
v-model="form.description"
|
||||||
|
id="description"
|
||||||
|
title="description"
|
||||||
|
:onError="form.errors.description"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Botón de Enviar -->
|
||||||
|
<div class="flex justify-end pt-6 border-t border-gray-100 dark:border-primary/20">
|
||||||
|
<PrimaryButton
|
||||||
|
v-text="$t(action)"
|
||||||
|
:class="{ 'opacity-25': form.processing }"
|
||||||
|
:disabled="form.processing"
|
||||||
|
type="submit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
106
src/pages/Admin/Events/PettyCashes/Index.vue
Normal file
106
src/pages/Admin/Events/PettyCashes/Index.vue
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { useSearcher } from '@Services/Api';
|
||||||
|
import { hasPermission } from '@Plugins/RolePermission';
|
||||||
|
import { getDate } from '@Controllers/DateController';
|
||||||
|
import { can, apiTo, viewTo, transl } from './Module'
|
||||||
|
|
||||||
|
import IconButton from '@Holos/Button/Icon.vue'
|
||||||
|
import DestroyView from '@Holos/Modal/Template/Destroy.vue';
|
||||||
|
import SearcherHead from '@Holos/Searcher.vue';
|
||||||
|
import Table from '@Holos/NewTable.vue';
|
||||||
|
import ShowView from './Modals/Show.vue';
|
||||||
|
|
||||||
|
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||||
|
import Searcher from '@Holos/Searcher.vue';
|
||||||
|
import Adding from '@Holos/Button/ButtonRh.vue';
|
||||||
|
|
||||||
|
/** Propiedades */
|
||||||
|
const models = ref([]);
|
||||||
|
|
||||||
|
/** Referencias */
|
||||||
|
const showModal = ref(false);
|
||||||
|
const destroyModal = ref(false);
|
||||||
|
|
||||||
|
/** Métodos */
|
||||||
|
const searcher = useSearcher({
|
||||||
|
url: apiTo('index'),
|
||||||
|
onSuccess: (r) => models.value = r.models,
|
||||||
|
onError: () => models.value = []
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Ciclos */
|
||||||
|
onMounted(() => {
|
||||||
|
searcher.search();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-extrabold text-gray-900 dark:text-primary-dt">{{ $t('petty_cash.title') }}</h1>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-primary-dt/70">{{ $t('petty_cash.description') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RouterLink :to="viewTo({ name: 'create' })">
|
||||||
|
<Adding text="Nueva Caja Chica" />
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search Card -->
|
||||||
|
<div class="pt-2 w-full">
|
||||||
|
<Searcher @search="(x) => searcher.search(x)">
|
||||||
|
</Searcher>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- List Card -->
|
||||||
|
<div class="pt-2 w-full">
|
||||||
|
<Table :items="models" :processing="searcher.processing" @send-pagination="(page) => searcher.pagination(page)">
|
||||||
|
<template #head>
|
||||||
|
<th v-text="$t('name')" />
|
||||||
|
<th v-text="$t('description')" />
|
||||||
|
<th v-text="$t('available_budget')" />
|
||||||
|
<th v-text="$t('company')" />
|
||||||
|
<th class="w-32 text-center" v-text="$t('actions')" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body="{ items }">
|
||||||
|
<tr v-for="model in items" class="table-row">
|
||||||
|
<td>{{ model.name }}</td>
|
||||||
|
<td>{{ model.description ?? '-' }}</td>
|
||||||
|
<td>${{ model.available_budget ?? 0 }}</td>
|
||||||
|
<td>{{ model.company?.name }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="table-actions">
|
||||||
|
<IconButton icon="visibility" :title="$t('crud.show')" @click="showModal.open(model)"
|
||||||
|
outline />
|
||||||
|
<RouterLink class="h-fit"
|
||||||
|
:to="viewTo({ name: 'edit', params: { id: model.id } })">
|
||||||
|
<IconButton icon="edit" :title="$t('crud.edit')" outline />
|
||||||
|
</RouterLink>
|
||||||
|
<IconButton icon="delete" :title="$t('crud.destroy')"
|
||||||
|
@click="destroyModal.open(model)" outline />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #empty>
|
||||||
|
<td colspan="5" class="py-12 text-center">
|
||||||
|
<div class="text-gray-500 dark:text-primary-dt/70">
|
||||||
|
<GoogleIcon name="account_balance_wallet" class="w-12 h-12 mx-auto mb-4 text-gray-400" />
|
||||||
|
<p class="text-lg font-medium">No se encontraron cajas chicas</p>
|
||||||
|
<p class="text-sm mt-1">Intenta ajustar los filtros de búsqueda</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<ShowView ref="showModal" />
|
||||||
|
|
||||||
|
<DestroyView ref="destroyModal" subtitle="name"
|
||||||
|
:to="(petty_cash) => apiTo('destroy', { petty_cash })" @update="searcher.search()" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
93
src/pages/Admin/Events/PettyCashes/Modals/Show.vue
Normal file
93
src/pages/Admin/Events/PettyCashes/Modals/Show.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { getDateTime } from '@Controllers/DateController';
|
||||||
|
|
||||||
|
import Header from '@Holos/Modal/Elements/Header.vue';
|
||||||
|
import ShowModal from '@Holos/Modal/Show.vue';
|
||||||
|
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
||||||
|
|
||||||
|
/** Eventos */
|
||||||
|
const emit = defineEmits([
|
||||||
|
'close',
|
||||||
|
'reload'
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** Propiedades */
|
||||||
|
const model = ref(null);
|
||||||
|
|
||||||
|
/** Referencias */
|
||||||
|
const modalRef = ref(null);
|
||||||
|
|
||||||
|
/** Métodos */
|
||||||
|
function close() {
|
||||||
|
model.value = null;
|
||||||
|
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Exposiciones */
|
||||||
|
defineExpose({
|
||||||
|
open: (data) => {
|
||||||
|
model.value = data;
|
||||||
|
modalRef.value.open();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ShowModal
|
||||||
|
ref="modalRef"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<div v-if="model">
|
||||||
|
<Header
|
||||||
|
:title="model.name"
|
||||||
|
:subtitle="model.company?.name ?? '-'"
|
||||||
|
>
|
||||||
|
<div class="flex w-full flex-col">
|
||||||
|
<div class="flex w-full justify-center items-center">
|
||||||
|
<GoogleIcon
|
||||||
|
class="text-6xl text-primary"
|
||||||
|
name="account_balance_wallet"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Header>
|
||||||
|
<div class="flex w-full p-4">
|
||||||
|
<GoogleIcon
|
||||||
|
class="text-xl text-success"
|
||||||
|
name="account_balance_wallet"
|
||||||
|
/>
|
||||||
|
<div class="pl-3">
|
||||||
|
<p class="font-bold text-lg leading-none pb-2">
|
||||||
|
{{ $t('details') }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>{{ $t('name') }}: </b>
|
||||||
|
{{ model.name }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>{{ $t('description') }}: </b>
|
||||||
|
{{ model.description }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>{{ $t('company') }}: </b>
|
||||||
|
{{ model.company?.name ?? '-' }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>{{ $t('available_budget') }}: </b>
|
||||||
|
${{ model.available_budget ?? 0 }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>{{ $t('created_at') }}: </b>
|
||||||
|
{{ getDateTime(model.created_at) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>{{ $t('updated_at') }}: </b>
|
||||||
|
{{ getDateTime(model.updated_at) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ShowModal>
|
||||||
|
</template>
|
||||||
21
src/pages/Admin/Events/PettyCashes/Module.js
Normal file
21
src/pages/Admin/Events/PettyCashes/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(`admin.petty-cashes.${name}`, params)
|
||||||
|
|
||||||
|
// Ruta visual
|
||||||
|
const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `admin.events.pettyCashes.${name}`, params, query })
|
||||||
|
|
||||||
|
// Obtener traducción del componente
|
||||||
|
const transl = (str) => lang(`pettyCashes.${str}`)
|
||||||
|
|
||||||
|
// Determina si un usuario puede hacer algo no en base a los permisos
|
||||||
|
const can = (permission) => hasPermission(`pettyCashes.${permission}`)
|
||||||
|
|
||||||
|
export {
|
||||||
|
can,
|
||||||
|
viewTo,
|
||||||
|
apiTo,
|
||||||
|
transl
|
||||||
|
}
|
||||||
@ -1,183 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref, computed } from 'vue';
|
|
||||||
import GoogleIcon from '@Shared/GoogleIcon.vue';
|
|
||||||
|
|
||||||
// Estado del presupuesto
|
|
||||||
const budgetItems = ref([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
concept: 'Ej. Catering para evento X',
|
|
||||||
amount: 1500.00
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
let nextId = 5;
|
|
||||||
|
|
||||||
// Computed para calcular el total
|
|
||||||
const totalBudget = computed(() => {
|
|
||||||
return budgetItems.value.reduce((total, item) => {
|
|
||||||
return total + (parseFloat(item.amount) || 0);
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Función para agregar nueva fila
|
|
||||||
const addBudgetItem = () => {
|
|
||||||
budgetItems.value.push({
|
|
||||||
id: nextId++,
|
|
||||||
concept: '',
|
|
||||||
amount: 0
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Función para eliminar fila
|
|
||||||
const removeBudgetItem = (id) => {
|
|
||||||
if (budgetItems.value.length > 1) {
|
|
||||||
budgetItems.value = budgetItems.value.filter(item => item.id !== id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Función para guardar presupuesto
|
|
||||||
const saveBudget = () => {
|
|
||||||
// Filtrar elementos vacíos
|
|
||||||
const validItems = budgetItems.value.filter(item =>
|
|
||||||
item.concept.trim() !== '' && item.amount > 0
|
|
||||||
);
|
|
||||||
|
|
||||||
if (validItems.length === 0) {
|
|
||||||
alert('Por favor, agrega al menos un elemento al presupuesto');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Guardar presupuesto:', {
|
|
||||||
items: validItems,
|
|
||||||
total: totalBudget.value
|
|
||||||
});
|
|
||||||
|
|
||||||
// Aquí implementarías la lógica para guardar
|
|
||||||
alert(`Presupuesto guardado correctamente. Total: $${totalBudget.value.toFixed(2)}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Función para formatear moneda
|
|
||||||
const formatCurrency = (amount) => {
|
|
||||||
return new Intl.NumberFormat('es-MX', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: 'USD'
|
|
||||||
}).format(amount);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="p-6 max-w-auto mx-auto">
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<div>
|
|
||||||
<h1 class="text-4xl font-extrabold text-gray-900 dark:text-primary-dt">Asignación de Presupuesto Anual</h1>
|
|
||||||
<p class="mt-1 text-sm text-gray-500 dark:text-primary-dt/70">Gestiona y asigna el presupuesto anual para eventos y actividades</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Card principal -->
|
|
||||||
<section class="mt-6 bg-white rounded-lg shadow-sm p-6 dark:bg-primary-d dark:border-primary/20 dark:text-primary-dt">
|
|
||||||
|
|
||||||
<!-- Formulario de presupuesto -->
|
|
||||||
<div class="space-y-6">
|
|
||||||
|
|
||||||
<!-- Filas de presupuesto -->
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in budgetItems"
|
|
||||||
:key="item.id"
|
|
||||||
class="grid grid-cols-1 md:grid-cols-12 gap-4 items-end"
|
|
||||||
>
|
|
||||||
|
|
||||||
<!-- Campo Concepto -->
|
|
||||||
<div class="md:col-span-5">
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-primary-dt mb-2">
|
|
||||||
Concepto
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
v-model="item.concept"
|
|
||||||
type="text"
|
|
||||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-[#2563eb] focus:border-transparent dark:bg-primary-d dark:border-primary/20 dark:text-primary-dt"
|
|
||||||
:placeholder="index === 0 ? 'Ej. Catering para evento X' : 'Ingresa el concepto'"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Campo Monto -->
|
|
||||||
<div class="md:col-span-4">
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-primary-dt mb-2">
|
|
||||||
Monto (USD)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
v-model.number="item.amount"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step="0.01"
|
|
||||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-[#2563eb] focus:border-transparent dark:bg-primary-d dark:border-primary/20 dark:text-primary-dt"
|
|
||||||
placeholder="0.00"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Botón de eliminar -->
|
|
||||||
<div class="md:col-span-3 flex justify-end">
|
|
||||||
<button
|
|
||||||
v-if="budgetItems.length > 1"
|
|
||||||
@click="removeBudgetItem(item.id)"
|
|
||||||
type="button"
|
|
||||||
class="p-2 text-red-500 hover:text-red-700 hover:bg-red-50 rounded-lg transition-colors dark:text-red-400 dark:hover:text-red-300 dark:hover:bg-red-900/20"
|
|
||||||
title="Eliminar fila"
|
|
||||||
>
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Resumen del presupuesto -->
|
|
||||||
<div class="mt-8 p-4 bg-gray-50 rounded-lg dark:bg-primary/5">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<GoogleIcon class="text-gray-600 dark:text-primary-dt text-xl" name="account_balance_wallet" />
|
|
||||||
<span class="text-lg font-medium text-gray-800 dark:text-primary-dt">Total del Presupuesto:</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-2xl font-bold text-[#2563eb] dark:text-primary-dt">
|
|
||||||
{{ formatCurrency(totalBudget) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Botones de acción -->
|
|
||||||
<div class="flex items-center justify-between pt-6 border-t border-gray-100 dark:border-primary/20">
|
|
||||||
|
|
||||||
<!-- Botón Agregar más -->
|
|
||||||
<button
|
|
||||||
@click="addBudgetItem"
|
|
||||||
type="button"
|
|
||||||
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-green-600 hover:bg-green-700 text-white font-medium shadow-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition-colors"
|
|
||||||
>
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16M20 12H4"></path>
|
|
||||||
</svg>
|
|
||||||
+ Agregar más
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Botón Guardar Presupuesto -->
|
|
||||||
<button
|
|
||||||
@click="saveBudget"
|
|
||||||
type="button"
|
|
||||||
class="inline-flex items-center gap-2 px-6 py-2 rounded-lg bg-[#2563eb] hover:bg-[#1e40af] text-white font-medium shadow-sm focus:outline-none focus:ring-2 focus:ring-[#2563eb] focus:ring-offset-2 transition-colors"
|
|
||||||
>
|
|
||||||
<GoogleIcon class="text-white text-xl" name="save" />
|
|
||||||
Guardar Presupuesto
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -218,22 +218,56 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
name: 'admin.events.index',
|
name: 'admin.events.index',
|
||||||
component: () => import('@Pages/Events/Index.vue'),
|
component: () => import('@Pages/Admin/Events/Index.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'assignamment',
|
path: 'pettyCashes',
|
||||||
name: 'admin.events.assignamment',
|
name: 'admin.events.pettyCashes',
|
||||||
component: () => import('@Pages/Events/Assignamment.vue'),
|
redirect: '/admin/events/pettyCashes',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'admin.events.pettyCashes.index',
|
||||||
|
component: () => import('@Pages/Admin/Events/PettyCashes/Index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'create',
|
||||||
|
name: 'admin.events.pettyCashes.create',
|
||||||
|
component: () => import('@Pages/Admin/Events/PettyCashes/Create.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'assignamment',
|
||||||
|
name: 'admin.events.pettyCashes.assignamment',
|
||||||
|
component: () => import('@Pages/Admin/Events/PettyCashes/Assignamment.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':id/edit',
|
||||||
|
name: 'admin.events.pettyCashes.edit',
|
||||||
|
component: () => import('@Pages/Admin/Events/PettyCashes/Edit.vue'),
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// path: 'expense',
|
||||||
|
// name: 'admin.events.expense',
|
||||||
|
// redirect: '/admin/events/expense',
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: '',
|
||||||
|
// name: 'admin.events.expense.index',
|
||||||
|
// component: () => import('@Pages/Admin/Events/ExpenseJustifications/Index.vue'),
|
||||||
|
// },
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
path: 'justification',
|
path: 'justification',
|
||||||
name: 'admin.events.justification',
|
name: 'admin.events.justification',
|
||||||
component: () => import('@Pages/Events/Justification.vue'),
|
component: () => import('@Pages/Admin/Events/Justification.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'reports',
|
path: 'reports',
|
||||||
name: 'admin.events.reports',
|
name: 'admin.events.reports',
|
||||||
component: () => import('@Pages/Events/Reports.vue'),
|
component: () => import('@Pages/Admin/Events/Reports.vue'),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user