269 lines
8.0 KiB
Vue
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>
|