eliminar empresa

This commit is contained in:
jose.lopez 2025-09-30 15:24:25 -06:00
parent 0b37323652
commit fefb045108
9 changed files with 310 additions and 121 deletions

View File

@ -42,9 +42,9 @@ onMounted(() => {
<template #leftSidebar> <template #leftSidebar>
<Section name="Principal"> <Section name="Principal">
<Link <Link
icon="grid_view" icon="grid_view"
name="Dashboard" name="Dashboard"
to="admin.dashboard.index" to="admin.dashboard.index"
/> />
<DropDown <DropDown
icon="people" icon="people"
@ -52,21 +52,21 @@ onMounted(() => {
to="admin.users.index" to="admin.users.index"
:collapsed="true" :collapsed="true"
> >
<Link <Link
icon="school" icon="school"
name="Historial Académico" name="Historial Académico"
to="admin.users.academic.index" to="admin.users.academic.index"
/> />
<Link <Link
icon="security" icon="security"
name="Seguridad y Salud" name="Seguridad y Salud"
to="admin.users.security" to="admin.users.security"
/> />
<Link <Link
icon="info" icon="info"
name="Información Adicional" name="Información Adicional"
to="admin.users.additional" to="admin.users.additional"
/> />
</DropDown> </DropDown>
</Section> </Section>
<Section name="Vacaciones"> <Section name="Vacaciones">
@ -83,6 +83,11 @@ onMounted(() => {
to="admin.courses.index" to="admin.courses.index"
:collapsed="true" :collapsed="true"
> >
<Link
icon="grid_view"
name="Solicitud de Cursos"
to="admin.courses.request"
/>
<Link <Link
icon="grid_view" icon="grid_view"
name="Calendario de Cursos" name="Calendario de Cursos"

View File

@ -23,34 +23,13 @@ const form = useForm({
participants_count: '', participants_count: '',
cost: '', cost: '',
observations: '', observations: '',
company_id: '',
petty_cash_id: '', petty_cash_id: '',
files: null, files: null,
}); });
const companies = ref([]);
const pettyCashes = ref([]); const pettyCashes = ref([]);
const availableBudget = ref(''); const availableBudget = ref('');
// Función para cargar cajas chicas por empresa
const loadPettyCashesByCompany = () => {
if (form.company_id?.id) {
api.catalog({
'pettyCash:byCompany': form.company_id.id
}, {
onSuccess: (r) => {
pettyCashes.value = r['pettyCash:byCompany'] ?? [];
// Limpiar selección de caja chica al cambiar empresa
form.petty_cash_id = '';
availableBudget.value = '';
}
});
}
};
// Watch para cargar cajas chicas cuando cambie la empresa
watch(() => form.company_id, loadPettyCashesByCompany, { deep: true });
// Watch para actualizar el presupuesto disponible cuando cambie la caja chica // Watch para actualizar el presupuesto disponible cuando cambie la caja chica
watch(() => form.petty_cash_id, (newValue) => { watch(() => form.petty_cash_id, (newValue) => {
availableBudget.value = newValue?.available_budget ?? 0; availableBudget.value = newValue?.available_budget ?? 0;
@ -83,7 +62,6 @@ const removeFile = () => {
function submit() { function submit() {
form.transform(data => ({ form.transform(data => ({
...data, ...data,
company_id: data.company_id?.id,
petty_cash_id: data.petty_cash_id?.id petty_cash_id: data.petty_cash_id?.id
})).post(apiTo('store-expense-justification'), { })).post(apiTo('store-expense-justification'), {
onSuccess: () => { onSuccess: () => {
@ -96,9 +74,9 @@ function submit() {
/** Ciclos */ /** Ciclos */
onMounted(() => { onMounted(() => {
api.catalog({ api.catalog({
'company:all': null 'pettyCash:all': null
}, { }, {
onSuccess: (r) => companies.value = r['company:all'] ?? [] onSuccess: (r) => pettyCashes.value = r['pettyCash:all'] ?? []
}); });
}); });
</script> </script>
@ -157,16 +135,7 @@ onMounted(() => {
/> />
<!-- Detalles Financieros y Participantes --> <!-- Detalles Financieros y Participantes -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<Selectable
v-model="form.company_id"
id="company_id"
title="Empresa"
:onError="form.errors.company_id"
:options="companies"
required
/>
<Input <Input
v-model.number="form.participants_count" v-model.number="form.participants_count"

View File

@ -15,32 +15,17 @@ const router = useRouter();
const form = useForm({ const form = useForm({
name: '', name: '',
description: '', description: '',
company_id: '',
}); });
const companies = ref([]);
/** Métodos */ /** Métodos */
function submit() { function submit() {
form.transform(data => ({ form.post(apiTo('store'), {
...data,
company_id: form.company_id?.id
})).post(apiTo('store'), {
onSuccess: () => { onSuccess: () => {
Notify.success(Lang('register.create.onSuccess')) Notify.success(Lang('register.create.onSuccess'))
router.push(viewTo({ name: 'index' })); router.push(viewTo({ name: 'index' }));
} }
}); });
} }
/** Ciclos */
onMounted(() => {
api.catalog({
'company:all': null
}, {
onSuccess: (r) => companies.value = r['company:all'] ?? []
});
});
</script> </script>
<template> <template>
@ -59,7 +44,6 @@ onMounted(() => {
<Form <Form
action="create" action="create"
:form="form" :form="form"
:companies="companies"
@submit="submit" @submit="submit"
/> />
</template> </template>

View File

@ -17,17 +17,11 @@ const form = useForm({
id: null, id: null,
name: '', name: '',
description: '', description: '',
company_id: '',
}); });
const companies = ref([]);
/** Métodos */ /** Métodos */
function submit() { function submit() {
form.transform(data => ({ form.put(apiTo('update', { petty_cash: form.id }), {
...data,
company_id: data.company_id?.id
})).put(apiTo('update', { petty_cash: form.id }), {
onSuccess: () => { onSuccess: () => {
Notify.success(Lang('register.edit.onSuccess')) Notify.success(Lang('register.edit.onSuccess'))
router.push(viewTo({ name: 'index' })); router.push(viewTo({ name: 'index' }));
@ -39,15 +33,6 @@ onMounted(() => {
api.get(apiTo('show', { petty_cash: vroute.params.id }), { api.get(apiTo('show', { petty_cash: vroute.params.id }), {
onSuccess: (r) => { onSuccess: (r) => {
form.fill(r.petty_cash) form.fill(r.petty_cash)
form.company_id = r.petty_cash.company
}
});
api.catalog({
'company:all': null,
}, {
onSuccess: (r) => {
companies.value = r['company:all'] ?? [];
} }
}); });
}) })
@ -67,7 +52,6 @@ onMounted(() => {
<Form <Form
action="edit" action="edit"
:form="form" :form="form"
:companies="companies"
@submit="submit" @submit="submit"
/> />
</template> </template>

View File

@ -4,7 +4,6 @@ import { transl } from './Module';
import PrimaryButton from '@Holos/Button/Primary.vue'; import PrimaryButton from '@Holos/Button/Primary.vue';
import Input from '@Holos/Form/Input.vue'; import Input from '@Holos/Form/Input.vue';
import Textarea from '@Holos/Form/Textarea.vue'; import Textarea from '@Holos/Form/Textarea.vue';
import Selectable from '@Holos/Form/Selectable.vue';
/** Eventos */ /** Eventos */
const emit = defineEmits([ const emit = defineEmits([
@ -35,23 +34,13 @@ function submit() {
<form @submit.prevent="submit" class="space-y-6"> <form @submit.prevent="submit" class="space-y-6">
<!-- Campos del formulario --> <!-- Campos del formulario -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> <Input
<Input v-model="form.name"
v-model="form.name" id="name"
id="name" title="name"
title="name" :onError="form.errors.name"
:onError="form.errors.name" required
required />
/>
<Selectable
v-model="form.company_id"
id="company_id"
title="company"
:onError="form.errors.company_id"
:options="companies"
required
/>
</div>
<Textarea <Textarea
v-model="form.description" v-model="form.description"

View File

@ -62,7 +62,6 @@ onMounted(() => {
<th v-text="$t('name')" /> <th v-text="$t('name')" />
<th v-text="$t('description')" /> <th v-text="$t('description')" />
<th v-text="$t('available_budget')" /> <th v-text="$t('available_budget')" />
<th v-text="$t('company')" />
<th class="w-32 text-center" v-text="$t('actions')" /> <th class="w-32 text-center" v-text="$t('actions')" />
</template> </template>
@ -71,7 +70,6 @@ onMounted(() => {
<td>{{ model.name }}</td> <td>{{ model.name }}</td>
<td>{{ model.description ?? '-' }}</td> <td>{{ model.description ?? '-' }}</td>
<td>${{ model.available_budget ?? 0 }}</td> <td>${{ model.available_budget ?? 0 }}</td>
<td>{{ model.company?.name }}</td>
<td> <td>
<div class="table-actions"> <div class="table-actions">
<IconButton icon="visibility" :title="$t('crud.show')" @click="showModal.open(model)" <IconButton icon="visibility" :title="$t('crud.show')" @click="showModal.open(model)"

View File

@ -42,17 +42,8 @@ defineExpose({
<div v-if="model"> <div v-if="model">
<Header <Header
:title="model.name" :title="model.name"
:subtitle="model.company?.name ?? '-'" :subtitle="model.description"
> />
<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"> <div class="flex w-full p-4">
<GoogleIcon <GoogleIcon
class="text-xl text-success" class="text-xl text-success"
@ -70,10 +61,6 @@ defineExpose({
<b>{{ $t('description') }}: </b> <b>{{ $t('description') }}: </b>
{{ model.description }} {{ model.description }}
</p> </p>
<p>
<b>{{ $t('company') }}: </b>
{{ model.company?.name ?? '-' }}
</p>
<p> <p>
<b>{{ $t('available_budget') }}: </b> <b>{{ $t('available_budget') }}: </b>
${{ model.available_budget ?? 0 }} ${{ model.available_budget ?? 0 }}

View File

@ -0,0 +1,268 @@
<script setup>
import { ref, computed, onMounted, watch } 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 Textarea from '@Holos/Form/Textarea.vue';
import Selectable from '@Holos/Form/Selectable.vue';
import PrimaryButton from '@Holos/Button/Primary.vue';
/** Definiciones */
const router = useRouter();
/** Propiedades */
const form = useForm({
name: '',
cost: '',
cost_currency: '',
description: '',
certification_name: '',
certification_type: '',
duration: '',
url: '',
department_id: '',
users: []
});
// Lista de departamentos y usuarios
const departments = ref([]);
const users = ref([]);
const currencies = ref([]);
// Computed para contar usuarios seleccionados
const selectedUsersCount = computed(() => {
return users.value.filter(u => u.selected).length;
});
/** Métodos */
function submit() {
form.transform(data => ({
...data,
department_id: form.department_id?.id,
cost_currency: form.cost_currency?.id,
users: users.value.filter(u => u.selected).map(u => u.id)
})).post(apiTo('store'), {
onSuccess: () => {
Notify.success(Lang('register.create.onSuccess'))
router.push(viewTo({ name: 'index' }));
}
});
}
// Función para cargar usuarios cuando se seleccione un departamento
const loadUsersByDepartment = () => {
api.catalog({
'user:byDepartment': form.department_id?.id
}, {
onSuccess: (r) => users.value = r['user:byDepartment'] ?? []
});
};
// Funciones para manejar selección de usuarios
const toggleUser = (user) => {
user.selected = !user.selected;
};
const selectAllUsers = () => {
const allSelected = users.value.every(u => u.selected);
users.value.forEach(u => u.selected = !allSelected);
};
// Watcher para cargar usuarios automáticamente cuando cambie el departamento
watch(() => form.department_id, () => {
loadUsersByDepartment();
});
/** Ciclos */
onMounted(() => {
api.catalog({
'department:all': null,
'currency:all': null
}, {
onSuccess: (r) => {
departments.value = r['department:all'] ?? []
currencies.value = r['currency:all'] ?? []
}
});
});
</script>
<template>
<PageHeader
title="Solicitud de Nuevo Curso"
>
<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">Completa la información para solicitar un nuevo curso de capacitación</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 -->
<form @submit.prevent="submit" class="space-y-6">
<!-- Campos del formulario en dos columnas -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Columna izquierda -->
<div class="space-y-6">
<Input
v-model="form.name"
id="name"
title="name"
:onError="form.errors.name"
required
/>
<Input
v-model="form.cost"
id="cost"
title="cost"
:onError="form.errors.cost"
type="number"
min="0"
step="0.01"
required
/>
<Selectable
v-model="form.cost_currency"
id="cost_currency"
title="currency"
:onError="form.errors.cost_currency"
:options="currencies"
/>
<Textarea
v-model="form.description"
id="description"
title="description"
:onError="form.errors.description"
required
/>
<Input
v-model="form.certification_name"
id="certification_name"
title="certification_name"
:onError="form.errors.certification_name"
/>
</div>
<!-- Columna derecha -->
<div class="space-y-6">
<Selectable
v-model="form.department_id"
id="department_id"
title="department"
:onError="form.errors.department_id"
:options="departments"
required
/>
<Input
v-model="form.certification_type"
id="certification_type"
title="certification_type"
:onError="form.errors.certification_type"
/>
<Input
v-model="form.duration"
id="duration"
title="duration"
:onError="form.errors.duration"
type="number"
min="1"
required
/>
<Input
v-model="form.url"
id="url"
title="url"
:onError="form.errors.url"
type="url"
/>
</div>
</div>
<!-- Sección de Personal a Capacitar -->
<div class="mt-8">
<div class="flex items-center justify-between mb-3">
<label class="block text-sm font-medium text-gray-700 dark:text-primary-dt">
Personal a capacitar
</label>
<button
v-if="users.length > 0"
type="button"
@click="selectAllUsers"
class="text-sm text-[#2563eb] hover:text-[#1e40af] font-medium"
>
{{ users.every(u => u.selected) ? 'Deseleccionar todo' : 'Seleccionar todo' }}
</button>
</div>
<div v-if="users.length > 0" class="border border-gray-300 rounded-lg p-4 max-h-48 overflow-y-auto dark:bg-primary-d dark:border-primary/20">
<div class="space-y-2">
<div
v-for="user in users"
:key="user.id"
@click="toggleUser(user)"
class="flex items-center p-2 rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-primary/10 transition-colors"
>
<div class="flex items-center h-5">
<input
:checked="user.selected"
type="checkbox"
class="w-4 h-4 text-[#2563eb] bg-gray-100 border-gray-300 rounded focus:ring-[#2563eb] dark:focus:ring-[#2563eb] dark:ring-offset-gray-800 focus:ring-2 dark:bg-primary-d dark:border-primary/20"
@click.stop
@change="toggleUser(user)"
>
</div>
<div class="ml-3 text-sm">
<label class="font-medium text-gray-900 dark:text-primary-dt cursor-pointer">
{{ user.name }} {{ user.paternal }} {{ user.maternal }}
</label>
<div class="text-xs text-gray-500 dark:text-primary-dt/70">
{{ user.email }}
</div>
</div>
</div>
</div>
</div>
<div v-else class="border border-gray-300 rounded-lg p-4 dark:bg-primary-d dark:border-primary/20">
<div class="text-center text-gray-500 dark:text-primary-dt/70">
<GoogleIcon class="w-8 h-8 mx-auto mb-2 text-gray-400" name="people" />
<p>Selecciona un departamento para ver el personal disponible</p>
</div>
</div>
<div v-if="users.length > 0" class="mt-2 text-sm text-gray-500 dark:text-primary-dt/70">
{{ selectedUsersCount }} usuario(s) seleccionado(s)
</div>
</div>
<!-- Botón de Enviar -->
<div class="flex justify-end pt-6 border-t border-gray-100 dark:border-primary/20">
<PrimaryButton
type="submit"
:processing="form.processing"
>
Enviar Solicitud
</PrimaryButton>
</div>
</form>
</section>
</template>

View File

@ -194,6 +194,11 @@ const router = createRouter({
name: 'admin.courses.index', name: 'admin.courses.index',
component: () => import('@Pages/Courses/Admin/Index.vue'), component: () => import('@Pages/Courses/Admin/Index.vue'),
}, },
{
path: 'request',
name: 'admin.courses.request',
component: () => import('@Pages/Courses/Admin/Request.vue'),
},
{ {
path: ':id/assignamment', path: ':id/assignamment',
name: 'admin.courses.assignamment', name: 'admin.courses.assignamment',