2025-09-30 21:27:17 +00:00

269 lines
8.0 KiB
Vue

<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>