343 lines
15 KiB
Vue

<script setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { getDateTime } from '@Controllers/DateController';
import { viewTo, apiTo } from '../Module';
import ProductService from '../services/ProductService';
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';
/** Eventos */
const emit = defineEmits([
'close',
'reload'
]);
/** Servicios */
const productService = new ProductService();
const router = useRouter();
/** Propiedades */
const model = ref(null);
const loading = ref(false);
/** Referencias */
const modalRef = ref(null);
/** Métodos */
function close() {
model.value = null;
emit('close');
}
/** Función para actualizar el estado del producto */
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 productService.updateStatus(item.id, newStatus);
// Actualizar el modelo local
item.is_active = newStatus;
// Notificación de éxito
const statusText = newStatus ? 'activado' : 'desactivado';
Notify.success(
`Producto "${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 del producto';
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 producto */
function editProduct() {
const editUrl = viewTo({ name: 'edit', params: { id: model.value.id } });
router.push(editUrl);
close();
}
/** Función para duplicar producto */
async function duplicateProduct() {
if (loading.value) return;
try {
loading.value = true;
await productService.duplicate(model.value.id);
Notify.success(
`Producto "${model.value.code}" duplicado exitosamente`,
'Producto duplicado'
);
emit('reload');
close();
} catch (error) {
console.error('Error duplicando producto:', error);
Notify.error('Error al duplicar el producto');
} finally {
loading.value = false;
}
}
/** Función para formatear atributos */
const formatAttributesDisplay = (attributes) => {
if (!attributes || typeof attributes !== 'object') return [];
return Object.entries(attributes).map(([key, value]) => ({ key, value }));
};
/** 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-blue-500 to-purple-600 flex items-center justify-center">
<GoogleIcon
class="text-white text-3xl"
name="inventory_2"
/>
</div>
</div>
</div>
</Header>
<div class="flex w-full p-4 space-y-6">
<!-- Información básica -->
<div class="w-full space-y-6">
<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>SKU: </b>
<code class="font-mono text-sm bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">
{{ model.sku }}
</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>
<!-- Atributos personalizados -->
<div v-if="model.attributes && Object.keys(model.attributes).length > 0" class="pt-6 border-t border-gray-200 dark:border-gray-700">
<div class="flex items-start">
<GoogleIcon
class="text-xl text-warning mt-1"
name="tune"
/>
<div class="pl-3 w-full">
<p class="font-bold text-lg leading-none pb-3">
{{ $t('Atributos Personalizados') }}
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div
v-for="attr in formatAttributesDisplay(model.attributes)"
:key="attr.key"
class="flex items-center p-3 bg-gray-50 dark:bg-gray-800 rounded-lg"
>
<div class="flex-1">
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">
{{ attr.key }}
</p>
<p class="font-semibold text-sm">
{{ attr.value }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Clasificaciones -->
<div v-if="(model.warehouse_classifications && model.warehouse_classifications.length > 0) || (model.comercial_classifications && model.comercial_classifications.length > 0)" class="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="category"
/>
<div class="pl-3 w-full">
<p class="font-bold text-lg leading-none pb-3">
{{ $t('Clasificaciones') }}
</p>
<!-- Clasificaciones de Almacén -->
<div v-if="model.warehouse_classifications && model.warehouse_classifications.length > 0" class="mb-4">
<p class="text-sm font-semibold text-gray-600 dark:text-gray-400 mb-2">
Clasificaciones de Almacén
</p>
<div class="flex flex-wrap gap-2">
<span
v-for="classification in model.warehouse_classifications"
:key="'w-' + classification.id"
class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100"
>
<code class="mr-2 text-xs">{{ classification.code }}</code>
{{ classification.name }}
</span>
</div>
</div>
<!-- Clasificaciones Comerciales -->
<div v-if="model.comercial_classifications && model.comercial_classifications.length > 0">
<p class="text-sm font-semibold text-gray-600 dark:text-gray-400 mb-2">
Clasificaciones Comerciales
</p>
<div class="flex flex-wrap gap-2">
<span
v-for="classification in model.comercial_classifications"
:key="'c-' + classification.id"
class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-purple-100 text-purple-800 dark:bg-purple-800 dark:text-purple-100"
>
<code class="mr-2 text-xs">{{ classification.code }}</code>
{{ classification.name }}
</span>
</div>
</div>
</div>
</div>
</div>
<!-- Acciones rápidas -->
<div class="pt-6 border-t border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-center gap-3">
<Button
:variant="'outline'"
:color="'primary'"
:loading="loading"
@click="editProduct"
>
<GoogleIcon name="edit" class="mr-2" />
{{ $t('crud.edit') }}
</Button>
<Button
:variant="'outline'"
:color="'info'"
:loading="loading"
@click="duplicateProduct"
>
<GoogleIcon name="content_copy" class="mr-2" />
{{ $t('Duplicar') }}
</Button>
</div>
</div>
</div>
</div>
</div>
</ShowModal>
</template>