362 lines
16 KiB
Vue

<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { getDateTime } from '@Controllers/DateController';
import { viewTo, apiTo } from '../Module';
import ComercialClassificationsService from '../services/ComercialClassificationsService';
import Notify from '@Plugins/Notify';
import Header from '@Holos/Modal/Elements/Header.vue';
import ShowModal from '@Holos/Modal/Show.vue';
import GoogleIcon from '@Shared/GoogleIcon.vue';
import Button from '@Holos/Button/Button.vue';
import IconButton from '@Holos/Button/Icon.vue';
import DestroyView from '@Holos/Modal/Template/Destroy.vue';
/** Eventos */
const emit = defineEmits([
'close',
'reload'
]);
/** Servicios */
const classificationsService = new ComercialClassificationsService();
const router = useRouter();
/** Propiedades */
const model = ref(null);
const loading = ref(false);
const deletingChildId = ref(null); // Para mostrar loading específico en subcategoría que se está eliminando
/** Referencias */
const modalRef = ref(null);
const destroyModal = ref(null);
/** Métodos */
function close() {
model.value = null;
emit('close');
}
/** Función para actualizar el estado de la clasificación */
async function toggleStatus(item) {
if (loading.value) return;
const newStatus = !item.is_active;
try {
loading.value = true;
// Usar el servicio para actualizar el estado
await classificationsService.updateStatus(item.id, newStatus);
// Actualizar el modelo local
item.is_active = newStatus;
// Notificación de éxito
const statusText = newStatus ? 'activada' : 'desactivada';
Notify.success(
`Clasificación "${item.code}" ${statusText} exitosamente`,
'Estado actualizado'
);
// Emitir evento para recargar la lista principal si es necesario
emit('reload');
} catch (error) {
console.error('Error actualizando estado:', error);
// Manejo específico de errores según la estructura de tu API
let errorMessage = 'Error al actualizar el estado de la clasificación';
let errorTitle = 'Error';
if (error?.response?.data) {
const errorData = error.response.data;
// Caso 1: Error con estructura específica de tu API
if (errorData.status === 'error') {
if (errorData.errors) {
// Errores de validación - extraer el primer error
const firstField = Object.keys(errorData.errors)[0];
const firstError = errorData.errors[firstField];
errorMessage = Array.isArray(firstError) ? firstError[0] : firstError;
errorTitle = 'Error de validación';
} else if (errorData.message) {
// Mensaje general del error
errorMessage = errorData.message;
errorTitle = 'Error del servidor';
}
}
// Caso 2: Otros formatos de error
else if (errorData.message) {
errorMessage = errorData.message;
}
} else if (error?.message) {
// Error genérico de la petición (red, timeout, etc.)
errorMessage = `Error de conexión: ${error.message}`;
errorTitle = 'Error de red';
}
// Notificación de error
Notify.error(errorMessage, errorTitle);
} finally {
loading.value = false;
}
}
/** Función para editar subcategoría */
function editSubcategory(child) {
// Navegar a la vista de edición de la subcategoría
const editUrl = viewTo({ name: 'edit', params: { id: child.id } });
router.push(editUrl);
// Cerrar el modal actual
close();
}
/** Función para eliminar subcategoría */
function deleteSubcategory(child) {
// Marcar cuál subcategoría se va a eliminar para mostrar loading
deletingChildId.value = child.id;
destroyModal.value.open(child);
}
/** Función para recargar después de eliminar - Mejorada */
async function onSubcategoryDeleted() {
try {
// Mostrar que se está procesando
const deletedId = deletingChildId.value;
// Recargar los datos de la clasificación actual para obtener subcategorías actualizadas
const response = await classificationsService.getById(model.value.id);
// Actualizar el modelo local con los datos frescos
if (response && response.comercial_classification) {
model.value = response.comercial_classification;
// Notificación de éxito con información específica
const deletedCount = model.value.children ? model.value.children.length : 0;
Notify.success(
`Subcategoría eliminada exitosamente. ${deletedCount} subcategorías restantes.`,
'Eliminación exitosa'
);
}
// Emitir evento para recargar la lista principal
emit('reload');
} catch (error) {
console.error('Error recargando datos después de eliminar:', error);
// En caso de error, cerrar modal y recargar lista principal
close();
emit('reload');
// Notificación de error
Notify.error(
'Error al actualizar la vista. Los datos se han actualizado correctamente.',
'Error de actualización'
);
} finally {
// Limpiar el estado de eliminación
deletingChildId.value = null;
}
}
/** Función para cancelar eliminación */
function onDeleteCancelled() {
// Limpiar el estado de eliminación si se cancela
deletingChildId.value = null;
}
/** Función para renderizar la jerarquía de hijos */
const renderChildren = (children, level = 1) => {
if (!children || children.length === 0) return null;
return children.map(child => ({
...child,
level,
children: renderChildren(child.children, level + 1)
}));
};
/** Exposiciones */
defineExpose({
open: (data) => {
model.value = data;
modalRef.value.open();
}
});
</script>
<template>
<ShowModal
ref="modalRef"
@close="close"
>
<div v-if="model">
<Header
:title="model.code"
:subtitle="model.name"
>
<div class="flex w-full flex-col">
<div class="flex w-full justify-center items-center">
<div class="w-24 h-24 rounded-full bg-gradient-to-br from-purple-500 to-indigo-600 flex items-center justify-center">
<GoogleIcon
class="text-white text-3xl"
name="category"
/>
</div>
</div>
</div>
</Header>
<div class="flex w-full p-4 space-y-6">
<!-- Información básica -->
<div class="w-full">
<div class="flex items-start">
<GoogleIcon
class="text-xl text-success mt-1"
name="info"
/>
<div class="pl-3 w-full">
<p class="font-bold text-lg leading-none pb-3">
{{ $t('details') }}
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p>
<b>{{ $t('code') }}: </b>
<code class="font-mono text-sm bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">
{{ model.code }}
</code>
</p>
<p class="mt-2">
<b>{{ $t('name') }}: </b>
{{ model.name }}
</p>
<p class="mt-2" v-if="model.description">
<b>{{ $t('description') }}: </b>
{{ model.description }}
</p>
</div>
<div>
<p>
<b>{{ $t('status') }}: </b>
<Button
:variant="'smooth'"
:color="model.is_active ? 'success' : 'danger'"
:size="'sm'"
:loading="loading"
@click="toggleStatus(model)"
>
{{ model.is_active ? $t('Activo') : $t('Inactivo') }}
</Button>
</p>
<p class="mt-2">
<b>{{ $t('created_at') }}: </b>
{{ getDateTime(model.created_at) }}
</p>
<p class="mt-2">
<b>{{ $t('updated_at') }}: </b>
{{ getDateTime(model.updated_at) }}
</p>
</div>
</div>
</div>
</div>
<!-- Clasificaciones hijas -->
<div v-if="model.children && model.children.length > 0" class="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
<div class="flex items-start">
<GoogleIcon
class="text-xl text-primary mt-1"
name="account_tree"
/>
<div class="pl-3 w-full">
<p class="font-bold text-lg leading-none pb-3">
{{ $t('Subclasificaciones') }}
</p>
<div class="space-y-2">
<div
v-for="child in model.children"
:key="child.id"
class="flex items-center p-3 bg-gray-50 dark:bg-gray-800 rounded-lg transition-all duration-300"
:class="{
'opacity-50 pointer-events-none': deletingChildId === child.id,
'bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-700': deletingChildId === child.id
}"
>
<div class="flex-1">
<div class="flex items-center">
<code class="font-mono text-sm bg-white dark:bg-gray-700 px-2 py-1 rounded mr-3">
{{ child.code }}
</code>
<span class="font-semibold">{{ child.name }}</span>
<!-- Indicador de eliminación -->
<span v-if="deletingChildId === child.id" class="ml-2 text-xs bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100 px-2 py-1 rounded-full animate-pulse">
Eliminando...
</span>
</div>
<p v-if="child.description" class="text-sm text-gray-600 dark:text-gray-400 mt-1">
{{ child.description }}
</p>
</div>
<!-- Botones de acción para subcategorías -->
<div class="flex items-center space-x-2 ml-4">
<!-- Botón de estado -->
<Button
:variant="'smooth'"
:color="child.is_active ? 'success' : 'danger'"
:size="'sm'"
:loading="loading && deletingChildId !== child.id"
:disabled="deletingChildId === child.id"
@click="toggleStatus(child)"
>
{{ child.is_active ? $t('Activo') : $t('Inactivo') }}
</Button>
<!-- Botón editar -->
<IconButton
icon="edit"
:title="$t('crud.edit')"
:disabled="deletingChildId === child.id"
@click="editSubcategory(child)"
outline
size="sm"
/>
<!-- Botón eliminar -->
<IconButton
icon="delete"
:title="$t('crud.destroy')"
:loading="deletingChildId === child.id"
:disabled="deletingChildId && deletingChildId !== child.id"
@click="deleteSubcategory(child)"
outline
size="sm"
/>
</div>
</div>
<!-- Mensaje cuando no hay subcategorías (después de eliminar todas) -->
<div v-if="!model.children || model.children.length === 0"
class="text-center p-4 text-gray-500 dark:text-gray-400">
<GoogleIcon class="text-2xl mb-2" name="folder_open" />
<p class="text-sm">No hay subcategorías</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal de eliminación para subcategorías -->
<DestroyView
ref="destroyModal"
subtitle="name"
:to="(id) => apiTo('destroy', { comercial_classification: id })"
@update="onSubcategoryDeleted"
/>
</ShowModal>
</template>