You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

323 lines
7.5 KiB
Vue

2 months ago
<template>
<a-modal
v-model:open="visible"
:title="`Gestionar Cursos - ${areaNombre}`"
:confirm-loading="loading"
width="800px"
:footer="null"
@cancel="handleCancel"
class="course-modal"
>
<!-- Barra de búsqueda -->
<div class="search-section">
<a-input-search
v-model:value="searchText"
placeholder="Buscar cursos..."
@search="handleSearch"
style="width: 300px; margin-bottom: 16px"
>
<template #enterButton>
<SearchOutlined />
</template>
</a-input-search>
</div>
<!-- Lista de cursos -->
<div class="courses-container">
<a-spin :spinning="loading">
<div class="courses-list">
<div
v-for="curso in filteredCursos"
:key="curso.id"
:class="['course-item', { 'course-selected': isCursoSelected(curso.id) }]"
@click="toggleCurso(curso.id)"
>
<div class="course-info">
<div class="course-header">
<span class="course-codigo">{{ curso.codigo }}</span>
<a-tag :color="isCursoSelected(curso.id) ? 'green' : 'default'">
{{ isCursoSelected(curso.id) ? 'Agregado' : 'No agregado' }}
</a-tag>
</div>
<h4 class="course-nombre">{{ curso.nombre }}</h4>
</div>
<div class="course-actions">
<a-button
:type="isCursoSelected(curso.id) ? 'danger' : 'primary'"
size="small"
@click.stop="toggleCurso(curso.id)"
>
<template #icon>
<CheckOutlined v-if="isCursoSelected(curso.id)" />
<PlusOutlined v-else />
</template>
{{ isCursoSelected(curso.id) ? 'Quitar' : 'Agregar' }}
</a-button>
</div>
</div>
</div>
</a-spin>
</div>
<!-- Resumen -->
<div class="summary-section">
<a-alert
:message="`${selectedCursosCount} cursos seleccionados de ${cursosDisponibles.length} disponibles`"
type="info"
show-icon
/>
</div>
<!-- Acciones -->
<div class="modal-footer">
<a-button @click="handleCancel">Cancelar</a-button>
<a-button type="primary" @click="handleSave" :loading="loading">
Guardar cambios
</a-button>
</div>
</a-modal>
</template>
<script setup>
import { ref, computed, watch, onMounted, nextTick } from 'vue'
import { message } from 'ant-design-vue'
import { SearchOutlined, PlusOutlined, CheckOutlined } from '@ant-design/icons-vue'
import { useAreaStore } from '../../../store/area.store'
const props = defineProps({
areaId: {
type: Number,
required: true
},
areaNombre: {
type: String,
default: ''
},
open: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:open', 'courses-updated'])
const visible = computed({
get: () => props.open,
set: (value) => emit('update:open', value)
})
const areaStore = useAreaStore()
const searchText = ref('')
const selectedCursos = ref([])
const loading = computed(() => areaStore.loading)
const cursosDisponibles = computed(() => areaStore.cursosDisponibles || [])
const cursosVinculados = computed(() => areaStore.cursosVinculados || [])
const filteredCursos = computed(() => {
if (!searchText.value) return cursosDisponibles.value
const searchLower = searchText.value.toLowerCase()
return cursosDisponibles.value.filter(curso =>
curso.nombre.toLowerCase().includes(searchLower) ||
curso.codigo.toLowerCase().includes(searchLower)
)
})
// Contador de cursos seleccionados
const selectedCursosCount = computed(() => selectedCursos.value.length)
// Verificar si un curso está seleccionado
const isCursoSelected = (cursoId) => {
return selectedCursos.value.includes(cursoId)
}
// Toggle de selección de curso
const toggleCurso = (cursoId) => {
const index = selectedCursos.value.indexOf(cursoId)
if (index > -1) {
selectedCursos.value.splice(index, 1)
} else {
selectedCursos.value.push(cursoId)
}
}
// Buscar cursos
const handleSearch = () => {
// La búsqueda se maneja en computed filteredCursos
}
// Cancelar
const handleCancel = () => {
visible.value = false
selectedCursos.value = []
searchText.value = ''
areaStore.clearState() // Limpiar estado
}
// Guardar cambios
const handleSave = async () => {
try {
const result = await areaStore.vincularCursos(props.areaId, selectedCursos.value)
if (result?.success) {
message.success('Cursos actualizados correctamente')
emit('courses-updated')
handleCancel()
} else {
message.error(areaStore.error || 'Error al guardar los cursos')
}
} catch (error) {
message.error('Error al guardar los cursos')
}
}
// Función para cargar cursos
const loadCursos = async () => {
if (props.areaId) {
await areaStore.fetchCursosPorArea(props.areaId)
// Inicializar selección con cursos ya vinculados
selectedCursos.value = [...areaStore.cursosVinculados]
}
}
// Cargar cursos cuando se abre el modal
watch(() => props.open, async (newVal) => {
if (newVal && props.areaId) {
// Usar nextTick para asegurar que el modal esté montado
await nextTick()
await loadCursos()
}
})
// También cargar cuando cambia el áreaId (por si cambia mientras el modal está abierto)
watch(() => props.areaId, async (newVal) => {
if (props.open && newVal) {
await loadCursos()
}
})
// Limpiar al cerrar
watch(() => props.open, (newVal) => {
if (!newVal) {
// Pequeño delay para permitir que la animación de cierre termine
setTimeout(() => {
selectedCursos.value = []
searchText.value = ''
areaStore.clearState()
}, 300)
}
})
// También cargar cursos cuando el componente se monta si ya está abierto
onMounted(() => {
if (props.open && props.areaId) {
loadCursos()
}
})
</script>
<style scoped>
.course-modal :deep(.ant-modal-body) {
padding: 20px;
}
.courses-container {
max-height: 400px;
overflow-y: auto;
margin-bottom: 16px;
border: 1px solid #f0f0f0;
border-radius: 8px;
padding: 8px;
}
.courses-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.course-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border: 1px solid #e8e8e8;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
background: white;
}
.course-item:hover {
background: #fafafa;
border-color: #d9d9d9;
}
.course-selected {
background-color: #f6ffed;
border-color: #b7eb8f;
}
.course-info {
flex: 1;
}
.course-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.course-codigo {
font-weight: 600;
color: #1890ff;
font-size: 14px;
}
.course-nombre {
margin: 0;
font-size: 14px;
color: #333;
}
.course-actions {
margin-left: 16px;
}
.search-section {
margin-bottom: 16px;
}
.summary-section {
margin-top: 16px;
margin-bottom: 16px;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
}
/* Scrollbar personalizado */
.courses-container::-webkit-scrollbar {
width: 6px;
}
.courses-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.courses-container::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.courses-container::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>