From c95d40787d7c0b3d8d6a77b311db7236a30efd17 Mon Sep 17 00:00:00 2001 From: "edgar.mendez" Date: Tue, 23 Sep 2025 16:18:45 -0600 Subject: [PATCH] WIP --- .env.example | 7 +- docker-compose.yml | 1 + src/layouts/AdminLayout.vue | 8 + src/pages/Warehouses/Index.vue | 386 +++++++++++++++++++++++++++++++++ src/pages/Warehouses/Module.js | 16 ++ src/router/Index.js | 37 ++++ 6 files changed, 452 insertions(+), 3 deletions(-) create mode 100644 src/pages/Warehouses/Index.vue create mode 100644 src/pages/Warehouses/Module.js diff --git a/.env.example b/.env.example index 94a2f7d..37712f0 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,13 @@ -VITE_API_URL=http://backend.holos.test:8080 -VITE_BASE_URL=http://frontend.holos.test +VITE_API_URL=http://localhost:8080 +VITE_BASE_URL=http://localhost:3000 VITE_REVERB_APP_ID= VITE_REVERB_APP_KEY= VITE_REVERB_APP_SECRET= -VITE_REVERB_HOST="backend.holos.test" +VITE_REVERB_HOST="localhost" VITE_REVERB_PORT=8080 VITE_REVERB_SCHEME=http VITE_REVERB_ACTIVE=false APP_PORT=3000 + diff --git a/docker-compose.yml b/docker-compose.yml index 050a3c6..25c89cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: - frontend-v1:/var/www/gols-frontend-v1/node_modules networks: - gols-network + mem_limit: 512m volumes: frontend-v1: driver: local diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue index 0b5a612..09880a8 100644 --- a/src/layouts/AdminLayout.vue +++ b/src/layouts/AdminLayout.vue @@ -81,6 +81,14 @@ onMounted(() => { to="admin.vacations.index" /> + +
+ +
+import { ref, computed, onMounted } from 'vue' +import GoogleIcon from '@Shared/GoogleIcon.vue' +import Searcher from '@Holos/Searcher.vue' +import Adding from '@Holos/Button/ButtonRh.vue' +import { api } from '@Services/Api' +import { apiTo } from './Module' + +// Reactive state +const warehouses = ref([]) +const loading = ref(false) +const error = ref('') +const inventoryMovements = ref([ + // kept mock movements for demo; in real app these would come from API + { + id: 1, + date: '2024-01-15T10:30:00', + type: 'entrada', + product: 'Laptop Dell Inspiron 15', + code: 'PROD-001', + quantity: 10, + warehouse: 'Almacén Principal', + reference: 'PO-2024-001', + user: 'Ana García', + status: 'completed' + }, + { + id: 2, + date: '2024-01-15T09:15:00', + type: 'salida', + product: 'Mouse Inalámbrico Logitech', + code: 'PROD-002', + quantity: 25, + warehouse: 'Almacén Norte', + reference: 'SO-2024-045', + user: 'Carlos López', + status: 'completed' + } +]) + +const stockByWarehouse = ref([]) + +// UI state +const activeTab = ref('warehouses') +const searchTerm = ref('') + +const fetchWarehousesFromApi = (q = '') => { + loading.value = true + error.value = '' + + api.get(apiTo('index'), { + params: { q }, + onStart: () => { + loading.value = true + }, + onSuccess: (data, fullPayload) => { + const payload = Array.isArray(data) ? data : (fullPayload && Array.isArray(fullPayload.data) ? fullPayload.data : []) + + warehouses.value = payload.map((w) => ({ + id: w.id, + code: w.code ?? `WH-${String(w.id).padStart(3, '0')}`, + name: w.name, + location: w.address ?? '', + type: w.type ?? '', + totalProducts: (w.classifications && Array.isArray(w.classifications)) ? w.classifications.length : 0, + totalValue: w.total_value ?? 0, + capacity: w.capacity ?? '0%', + status: w.is_active ? 'active' : 'inactive', + classifications: w.classifications ?? [] + })) + }, + onFail: (data) => { + error.value = data?.message || 'Error al obtener almacenes' + }, + onError: (err) => { + console.error('API Error:', err) + try { + error.value = err?.message || err?.data?.message || 'Error desconocido' + } catch (e) { + error.value = 'Error desconocido' + } + }, + onFinish: () => { + loading.value = false + } + }) +} + +onMounted(() => { + fetchWarehousesFromApi() +}) + +// Computed totals +const totalInventoryValue = computed(() => warehouses.value.reduce((sum, wh) => sum + (wh.totalValue || 0), 0)) +const totalProducts = computed(() => warehouses.value.reduce((sum, wh) => sum + (wh.totalProducts || 0), 0)) + +// Filtered movements by searchTerm +const filteredMovements = computed(() => { + const q = searchTerm.value.trim().toLowerCase() + if (!q) return inventoryMovements.value + return inventoryMovements.value.filter(m => { + return [m.product, m.code, m.warehouse, m.user, m.reference] + .filter(Boolean) + .some(f => f.toLowerCase().includes(q)) + }) +}) + +// Helpers +const parseCapacity = (cap) => { + if (typeof cap === 'string') return parseInt(cap.replace('%', ''), 10) || 0 + if (typeof cap === 'number') return Math.round(cap) + return 0 +} + +const getMovementIconClass = (type) => { + switch (type) { + case 'entrada': + return 'icon-arrow-down-left text-success' + case 'salida': + return 'icon-arrow-up-right text-destructive' + case 'transferencia': + return 'icon-refresh text-warning' + case 'ajuste': + return 'icon-trending-up text-primary' + default: + return 'icon-refresh' + } +} + +const movementVariant = (type) => { + const map = { entrada: 'default', salida: 'destructive', transferencia: 'secondary', ajuste: 'outline' } + return map[type] || 'default' +} + +const formatCurrency = (value) => { + try { + return new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(value) + } catch (e) { + return `$${value}` + } +} + + + \ No newline at end of file diff --git a/src/pages/Warehouses/Module.js b/src/pages/Warehouses/Module.js new file mode 100644 index 0000000..9630d8d --- /dev/null +++ b/src/pages/Warehouses/Module.js @@ -0,0 +1,16 @@ +import { lang } from '@Lang/i18n'; + +// Ruta API +const apiTo = (name, params = {}) => route(`warehouses.${name}`, params) + +// Ruta visual +const viewTo = ({ name = '', params = {}, query = {} }) => view({ name: `warehouses.${name}`, params, query }) + +// Obtener traducción del componente +const transl = (str) => lang(`warehouses.${str}`) + +export { + viewTo, + apiTo, + transl +} \ No newline at end of file diff --git a/src/router/Index.js b/src/router/Index.js index 99cac92..79ba040 100644 --- a/src/router/Index.js +++ b/src/router/Index.js @@ -306,6 +306,43 @@ const router = createRouter({ } ] }, + { + path: 'warehouses', + name: 'admin.warehouses', + meta: { + title: 'Bodegas', + icon: 'inventory_2', + }, + redirect: '/admin/warehouses', + children: [ + { + path: '', + name: 'admin.warehouses.index', + component: () => import('@Pages/Warehouses/Index.vue'), + + }, + { + path: 'create', + name: 'admin.warehouses.create', + component: () => import('@Pages/Admin/Roles/Index.vue'), + + meta: { + title: 'Crear', + icon: 'add', + }, + }, + { + path: ':id/edit', + name: 'admin.warehouses.edit', + component: () => import('@Pages/Admin/Roles/Index.vue'), + + meta: { + title: 'Editar', + icon: 'edit', + }, + } + ] + }, { path: 'roles', name: 'admin.roles',