diff --git a/src/components/Holos/Button/Button.vue b/src/components/Holos/Button/Button.vue index 128be0b..362eba4 100644 --- a/src/components/Holos/Button/Button.vue +++ b/src/components/Holos/Button/Button.vue @@ -23,6 +23,7 @@ interface Props { loading?: boolean; fullWidth?: boolean; iconOnly?: boolean; + asLink?: boolean; // Nueva prop para comportamiento de link } const props = withDefaults(defineProps(), { @@ -34,6 +35,7 @@ const props = withDefaults(defineProps(), { loading: false, fullWidth: false, iconOnly: false, + asLink: true, // Por defecto no es link }); @@ -42,8 +44,15 @@ const emit = defineEmits<{ }>(); function handleClick(event: MouseEvent) { + // Si es usado como link, no bloquear la navegación + if (props.asLink) { + emit('click', event); + return; + } + + // Para botones normales, validar estados if (props.disabled || props.loading) return; - emit('click', event); + } const buttonClasses = computed(() => { diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue index 58fc3a2..2b26cc4 100644 --- a/src/layouts/AdminLayout.vue +++ b/src/layouts/AdminLayout.vue @@ -93,6 +93,11 @@ onMounted(() => { name="Clasificaciones de almacenes" to="admin.warehouse-classifications.index" /> +
+import { onMounted, ref } from 'vue'; +import { useRouter, useRoute, RouterLink } from 'vue-router'; +import { api, useForm } from '@Services/Api'; +import { apiTo, transl, viewTo } from './Module'; + +import IconButton from '@Holos/Button/Icon.vue' +import PageHeader from '@Holos/PageHeader.vue'; +import GoogleIcon from '@Shared/GoogleIcon.vue'; +import Form from './Form.vue' + +/** Definidores */ +const router = useRouter(); +const route = useRoute(); + +/** Propiedades */ +const form = useForm({ + code: '', + name: '', + abbreviation: '', + type: 4, // Unidad por defecto + is_active: true +}); + +/** Métodos */ +function submit() { + form.transform(data => ({ + ...data, + type: data.type?.value || data.type, // Extraer solo el value si es objeto, sino usar tal como está + is_active: data.is_active ? 1 : 0 // Convertir boolean a número + })).post(apiTo('store'), { + onSuccess: () => { + Notify.success('Unidad de medida creada con éxito') + router.push(viewTo({ name: 'index' })); + } + }) +} + +/** Ciclos */ +onMounted(() => { + // Inicialización si es necesaria +}); + + \ No newline at end of file diff --git a/src/pages/UnitsMeasure/Edit.vue b/src/pages/UnitsMeasure/Edit.vue new file mode 100644 index 0000000..d18fd41 --- /dev/null +++ b/src/pages/UnitsMeasure/Edit.vue @@ -0,0 +1,104 @@ + + + \ No newline at end of file diff --git a/src/pages/UnitsMeasure/Form.vue b/src/pages/UnitsMeasure/Form.vue new file mode 100644 index 0000000..7e3f0bb --- /dev/null +++ b/src/pages/UnitsMeasure/Form.vue @@ -0,0 +1,108 @@ + + + \ No newline at end of file diff --git a/src/pages/UnitsMeasure/Index.vue b/src/pages/UnitsMeasure/Index.vue new file mode 100644 index 0000000..0d66f19 --- /dev/null +++ b/src/pages/UnitsMeasure/Index.vue @@ -0,0 +1,124 @@ + + + \ No newline at end of file diff --git a/src/pages/UnitsMeasure/Modals/Show.vue b/src/pages/UnitsMeasure/Modals/Show.vue new file mode 100644 index 0000000..e025418 --- /dev/null +++ b/src/pages/UnitsMeasure/Modals/Show.vue @@ -0,0 +1,210 @@ + + + \ No newline at end of file diff --git a/src/pages/UnitsMeasure/Module.js b/src/pages/UnitsMeasure/Module.js new file mode 100644 index 0000000..2130417 --- /dev/null +++ b/src/pages/UnitsMeasure/Module.js @@ -0,0 +1,23 @@ +import { lang } from '@Lang/i18n'; +import { hasPermission } from '@Plugins/RolePermission.js'; + +// Ruta API +const apiTo = (name, params = {}) => route(`units-of-measure.${name}`, params) + +// Ruta visual +const viewTo = ({ name = '', params = {}, query = {} }) => view({ + name: `admin.units-measure.${name}`, params, query +}) + +// Obtener traducción del componente +const transl = (str) => lang(`admin.units_measure.${str}`) + +// Control de permisos +const can = (permission) => hasPermission(`admin.units-measure.${permission}`) + +export { + can, + viewTo, + apiTo, + transl +} \ No newline at end of file diff --git a/src/pages/UnitsMeasure/services/UnitsMeasureService.js b/src/pages/UnitsMeasure/services/UnitsMeasureService.js new file mode 100644 index 0000000..fc0f3a6 --- /dev/null +++ b/src/pages/UnitsMeasure/services/UnitsMeasureService.js @@ -0,0 +1,227 @@ +/** + * Servicio para Units of Measure + * + * @author Sistema + * @version 1.0.0 + */ + +import { api, apiURL } from '@Services/Api'; + +export default class UnitsMeasureService { + + /** + * Obtener todas las unidades de medida + * @param {Object} params - Parámetros de la consulta + * @returns {Promise} Promesa con la respuesta + */ + async getAll(params = {}) { + return new Promise((resolve, reject) => { + api.get(apiURL('catalogs/units-of-measure'), { + params, + onSuccess: (response) => resolve(response), + onError: (error) => reject(error) + }); + }); + } + + /** + * Obtener una unidad de medida por ID + * @param {number} id - ID de la unidad de medida + * @returns {Promise} Promesa con la respuesta + */ + async getById(id) { + return new Promise((resolve, reject) => { + api.get(apiURL(`catalogs/units-of-measure/${id}`), { + onSuccess: (response) => resolve(response), + onError: (error) => reject(error) + }); + }); + } + + /** + * Crear una nueva unidad de medida + * @param {Object} data - Datos de la unidad de medida + * @param {string} data.code - Código de la unidad + * @param {string} data.name - Nombre de la unidad + * @param {string} data.abbreviation - Abreviación de la unidad + * @param {number} data.type - Tipo de medida (1-7) + * @param {boolean} data.is_active - Estado activo/inactivo + * @returns {Promise} Promesa con la respuesta + */ + async create(data) { + return new Promise((resolve, reject) => { + api.post(apiURL('catalogs/units-of-measure'), data, { + onSuccess: (response) => resolve(response), + onError: (error) => reject(error) + }); + }); + } + + /** + * Actualizar una unidad de medida existente + * @param {number} id - ID de la unidad de medida + * @param {Object} data - Datos actualizados + * @returns {Promise} Promesa con la respuesta + */ + async update(id, data) { + return new Promise((resolve, reject) => { + api.put(apiURL(`catalogs/units-of-measure/${id}`), data, { + onSuccess: (response) => resolve(response), + onError: (error) => reject(error) + }); + }); + } + + /** + * Eliminar una unidad de medida + * @param {number} id - ID de la unidad de medida + * @returns {Promise} Promesa con la respuesta + */ + async delete(id) { + return new Promise((resolve, reject) => { + api.delete(apiURL(`catalogs/units-of-measure/${id}`), { + onSuccess: (response) => resolve(response), + onError: (error) => reject(error) + }); + }); + } + + /** + * Actualizar el estado de una unidad de medida + * @param {number} id - ID de la unidad de medida + * @param {boolean} isActive - Nuevo estado + * @returns {Promise} Promesa con la respuesta + */ + async updateStatus(id, isActive) { + return new Promise((resolve, reject) => { + api.put(apiURL(`catalogs/units-of-measure/${id}`), { + data: { is_active: isActive }, + onSuccess: (response) => { + resolve(response); + }, + onError: (error) => { + // Mejorar el manejo de errores + const enhancedError = { + ...error, + timestamp: new Date().toISOString(), + action: 'updateStatus', + id: id, + is_active: isActive + }; + reject(enhancedError); + } + }); + }); + } + + /** + * Obtener tipos de medida disponibles desde la API + * @returns {Promise} Promesa con la respuesta + */ + async getUnitTypes() { + return new Promise((resolve, reject) => { + api.get(apiURL('catalogs/unit-types'), { + onSuccess: (response) => { + // Extraer los tipos de la respuesta y formatearlos para el Selectable + const unitTypes = response.data?.unit_types || response.unit_types || []; + const formattedTypes = unitTypes.map(type => ({ + value: type.id, + label: type.name + })); + resolve(formattedTypes); + }, + onError: (error) => reject(error) + }); + }); + } + + /** + * Obtener tipos de medida como objetos completos (para mapping) + * @returns {Promise} Promesa con los tipos completos + */ + async getUnitTypesMap() { + return new Promise((resolve, reject) => { + api.get(apiURL('catalogs/unit-types'), { + onSuccess: (response) => { + const unitTypes = response.data?.unit_types || response.unit_types || []; + // Crear un mapa para acceso rápido por ID + const typesMap = {}; + unitTypes.forEach(type => { + typesMap[type.id] = type; + }); + resolve(typesMap); + }, + onError: (error) => reject(error) + }); + }); + } + + /** + * Obtener tipos de medida disponibles (método legacy - mantener por compatibilidad) + * @returns {Array} Array con los tipos de medida + */ + getTypes() { + return [ + { id: 1, name: 'Distancia', description: 'Medidas de longitud (metros, kilómetros, etc.)' }, + { id: 2, name: 'Peso', description: 'Medidas de masa (kilogramos, gramos, etc.)' }, + { id: 3, name: 'Temperatura', description: 'Medidas de temperatura (grados Celsius, etc.)' }, + { id: 4, name: 'Unidad', description: 'Unidades discretas (piezas, unidades, etc.)' }, + { id: 5, name: 'Volumen', description: 'Medidas de volumen (litros, mililitros, etc.)' } + ]; + } + + /** + * Obtener unidades de medida activas + * @returns {Promise} Promesa con las unidades activas + */ + async getActive() { + return new Promise((resolve, reject) => { + this.getAll({ is_active: true }) + .then(response => { + const activeUnits = response.data?.units_of_measure?.data || []; + resolve(activeUnits.filter(unit => unit.is_active)); + }) + .catch(error => reject(error)); + }); + } + + /** + * Buscar unidades de medida por término + * @param {string} term - Término de búsqueda + * @returns {Promise} Promesa con los resultados + */ + async search(term) { + return new Promise((resolve, reject) => { + this.getAll({ search: term }) + .then(response => resolve(response)) + .catch(error => reject(error)); + }); + } + + /** + * Validar datos antes de enviar + * @param {Object} data - Datos a validar + * @returns {Object} Objeto con errores si los hay + */ + validate(data) { + const errors = {}; + + if (!data.code || data.code.trim() === '') { + errors.code = 'El código es requerido'; + } + + if (!data.name || data.name.trim() === '') { + errors.name = 'El nombre es requerido'; + } + + if (!data.abbreviation || data.abbreviation.trim() === '') { + errors.abbreviation = 'La abreviación es requerida'; + } + + if (!data.type || ![1, 2, 3, 4, 5, 6, 7].includes(data.type)) { + errors.type = 'El tipo de medida es requerido y debe ser válido'; + } + + return errors; + } +} \ No newline at end of file diff --git a/src/pages/Warehouses/Create.vue b/src/pages/Warehouses/Create.vue new file mode 100644 index 0000000..e719482 --- /dev/null +++ b/src/pages/Warehouses/Create.vue @@ -0,0 +1,66 @@ + + + \ No newline at end of file diff --git a/src/pages/Warehouses/Details.vue b/src/pages/Warehouses/Details.vue new file mode 100644 index 0000000..910e705 --- /dev/null +++ b/src/pages/Warehouses/Details.vue @@ -0,0 +1,457 @@ + + + \ No newline at end of file diff --git a/src/pages/Warehouses/Edit.vue b/src/pages/Warehouses/Edit.vue new file mode 100644 index 0000000..1adc8dd --- /dev/null +++ b/src/pages/Warehouses/Edit.vue @@ -0,0 +1,129 @@ + + + \ No newline at end of file diff --git a/src/pages/Warehouses/Form.vue b/src/pages/Warehouses/Form.vue new file mode 100644 index 0000000..15f8ba6 --- /dev/null +++ b/src/pages/Warehouses/Form.vue @@ -0,0 +1,338 @@ + + +