From 35a64ff7bf6c2cd18ca783b9e119aef2642f5813 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 08:05:59 +0000 Subject: [PATCH] feat: Reinstate local view toggles in module indexes This commit updates the module index pages to re-introduce local view toggle buttons, allowing you to temporarily switch between table and card visualizations for the current session. This change is based on your feedback to retain this flexibility alongside the new global default view settings. Key changes: - **Module Index Views (e.g., `EmpleadosIndex.vue`):** - Re-added icon-based toggle buttons for 'Table' and 'Card' views. - Styled these buttons using Tailwind CSS for a subtle and modern appearance. The active view's button is highlighted using the module's accent color. - Clicking these buttons updates a local `currentView` ref, which determines the displayed component (table or card). - This local selection overrides the global default view for the current session only and does not modify the saved default setting in the `useUi` store. - **Testing:** - Updated component tests for each module's index view (`AsistenciasIndex.spec.js`, `EmpleadosIndex.spec.js`, etc.). - Tests now verify: - Correct rendering and initial styling of the new toggle buttons based on the global default. - Local view switching functionality upon button clicks. - Correct update of button styling to reflect the active local view. - Confirmation that local view changes do not affect the global default view settings in the `useUi` store. This enhancement ensures that you can set a global default view for each module via settings, while still having the option to quickly toggle the view for your immediate needs without changing your saved preferences. --- ui/src/views/asistencias/AsistenciasIndex.vue | 28 ++++++- .../__tests__/AsistenciasIndex.spec.js | 81 +++++++++++++++++++ ui/src/views/empleados/EmpleadosIndex.vue | 26 +++++- .../__tests__/EmpleadosIndex.spec.js | 73 +++++++++++++++++ ui/src/views/planillas/PlanillasIndex.vue | 28 ++++++- .../__tests__/PlanillasIndex.spec.js | 75 +++++++++++++++++ ui/src/views/tareas/TareasIndex.vue | 28 ++++++- .../tareas/__tests__/TareasIndex.spec.js | 75 +++++++++++++++++ 8 files changed, 407 insertions(+), 7 deletions(-) diff --git a/ui/src/views/asistencias/AsistenciasIndex.vue b/ui/src/views/asistencias/AsistenciasIndex.vue index 087fc42..7da4e5f 100644 --- a/ui/src/views/asistencias/AsistenciasIndex.vue +++ b/ui/src/views/asistencias/AsistenciasIndex.vue @@ -7,7 +7,25 @@ - + +
+ + +
Cargando asistencias... @@ -95,7 +113,13 @@ const handleEditAsistencia = (asistenciaId) => { router.push({ name: 'asistencias-edit', params: { id: asistenciaId } }); }; -// Removed btnClass as manual toggle buttons are removed +const btnViewClass = (viewType) => { + const base = 'p-2 rounded-md transition-colors duration-150 ease-in-out'; + if (currentView.value === viewType) { + return `${base} bg-[var(--accent-color-asistencias)] text-white shadow-lg`; + } + return `${base} bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600`; +}; diff --git a/ui/src/views/asistencias/__tests__/AsistenciasIndex.spec.js b/ui/src/views/asistencias/__tests__/AsistenciasIndex.spec.js index 6854eb9..b04b9fa 100644 --- a/ui/src/views/asistencias/__tests__/AsistenciasIndex.spec.js +++ b/ui/src/views/asistencias/__tests__/AsistenciasIndex.spec.js @@ -132,4 +132,85 @@ describe('AsistenciasIndex.vue', () => { expect(wrapper.text()).toContain('No hay asistencias para mostrar'); }); }) + + describe('Local View Toggle Buttons', () => { + it('renders toggle buttons and reflects initial view from store (table)', async () => { + uiStoreMock.defaultViewAsistencias = 'table'; + const wrapper = mountComponent(); + await mockFetchAsistencias(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + + expect(tableViewButton.exists()).toBe(true); + expect(cardViewButton.exists()).toBe(true); + + // Check active class based on btnViewClass logic + // Active: bg-[var(--accent-color-asistencias)] text-white + // Inactive: bg-gray-200 text-gray-700 + expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-asistencias)]'); + expect(cardViewButton.classes()).toContain('bg-gray-200'); + }); + + it('renders toggle buttons and reflects initial view from store (card)', async () => { + uiStoreMock.defaultViewAsistencias = 'card'; + const wrapper = mountComponent(); + await mockFetchAsistencias(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + + expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-asistencias)]'); + expect(tableViewButton.classes()).toContain('bg-gray-200'); + }); + + it('switches to card view on button click and updates button styles, does not call global store action', async () => { + uiStoreMock.defaultViewAsistencias = 'table'; + asistenciasStoreMock.asistencias = [{ id: 1, empleado: 'Test' }]; + const wrapper = mountComponent(); + await mockFetchAsistencias(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + + await cardViewButton.trigger('click'); + await wrapper.vm.$nextTick(); + + expect(wrapper.findComponent({ name: 'CardAsistencia' }).exists()).toBe(true); + expect(wrapper.findComponent({ name: 'TablaAsistencias' }).exists()).toBe(false); + expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-asistencias)]'); + expect(tableViewButton.classes()).toContain('bg-gray-200'); + expect(mockSetDefaultViewAsistencias).not.toHaveBeenCalled(); + }); + + it('switches back to table view on button click and updates button styles, does not call global store action', async () => { + uiStoreMock.defaultViewAsistencias = 'card'; // Start with card view + asistenciasStoreMock.asistencias = [{ id: 1, empleado: 'Test' }]; + const wrapper = mountComponent(); + await mockFetchAsistencias(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + // Initially card view is active + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-asistencias)]'); + + + await tableViewButton.trigger('click'); + await wrapper.vm.$nextTick(); + + expect(wrapper.findComponent({ name: 'TablaAsistencias' }).exists()).toBe(true); + expect(wrapper.findComponent({ name: 'CardAsistencia' }).exists()).toBe(false); + expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-asistencias)]'); + expect(cardViewButton.classes()).toContain('bg-gray-200'); + expect(mockSetDefaultViewAsistencias).not.toHaveBeenCalled(); + }); + }); }) diff --git a/ui/src/views/empleados/EmpleadosIndex.vue b/ui/src/views/empleados/EmpleadosIndex.vue index a7240fe..90ec10b 100644 --- a/ui/src/views/empleados/EmpleadosIndex.vue +++ b/ui/src/views/empleados/EmpleadosIndex.vue @@ -19,7 +19,24 @@ - +
+ + +
@@ -88,6 +105,13 @@ const employees = empleados; // --- helpers --- // Removed btnClass as manual toggle buttons are removed +const btnViewClass = (viewType: 'card' | 'table') => { + const base = 'p-2 rounded-md transition-colors duration-150 ease-in-out'; + if (currentView.value === viewType) { + return `${base} bg-[var(--accent-color-empleados)] text-white shadow-lg`; + } + return `${base} bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600`; +}; // --- fetch inicial --- const fetchEmployees = async () => { diff --git a/ui/src/views/empleados/__tests__/EmpleadosIndex.spec.js b/ui/src/views/empleados/__tests__/EmpleadosIndex.spec.js index 0dc3202..496134a 100644 --- a/ui/src/views/empleados/__tests__/EmpleadosIndex.spec.js +++ b/ui/src/views/empleados/__tests__/EmpleadosIndex.spec.js @@ -132,4 +132,77 @@ describe('EmpleadosIndex.vue', () => { expect(wrapper.text()).toContain('No hay empleados para mostrar en la vista de tarjetas.'); }); }) + + describe('Local View Toggle Buttons', () => { + it('renders toggle buttons and reflects initial view from store (table)', async () => { + uiStoreMock.defaultViewEmpleados = 'table'; + const wrapper = mountComponent(); + // Wait for loading and reactivity + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + + expect(tableViewButton.exists()).toBe(true); + expect(cardViewButton.exists()).toBe(true); + expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-empleados)]'); + expect(cardViewButton.classes()).toContain('bg-gray-200'); + }); + + it('renders toggle buttons and reflects initial view from store (card)', async () => { + uiStoreMock.defaultViewEmpleados = 'card'; + const wrapper = mountComponent(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + + expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-empleados)]'); + expect(tableViewButton.classes()).toContain('bg-gray-200'); + }); + + it('switches to card view on button click and updates button styles, does not call global store action', async () => { + uiStoreMock.defaultViewEmpleados = 'table'; + empleadosStoreMock.empleados = [{ id: 1, nombre: 'Test' }]; + const wrapper = mountComponent(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + + await cardViewButton.trigger('click'); + await wrapper.vm.$nextTick(); + + expect(wrapper.findComponent({ name: 'CardEmpleado' }).exists()).toBe(true); + expect(wrapper.findComponent({ name: 'TablaEmpleados' }).exists()).toBe(false); + expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-empleados)]'); + expect(tableViewButton.classes()).toContain('bg-gray-200'); + expect(mockSetDefaultViewEmpleados).not.toHaveBeenCalled(); + }); + + it('switches back to table view on button click and updates button styles, does not call global store action', async () => { + uiStoreMock.defaultViewEmpleados = 'card'; // Start with card view + empleadosStoreMock.empleados = [{ id: 1, nombre: 'Test' }]; + const wrapper = mountComponent(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-empleados)]'); + + await tableViewButton.trigger('click'); + await wrapper.vm.$nextTick(); + + expect(wrapper.findComponent({ name: 'TablaEmpleados' }).exists()).toBe(true); + expect(wrapper.findComponent({ name: 'CardEmpleado' }).exists()).toBe(false); + expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-empleados)]'); + expect(cardViewButton.classes()).toContain('bg-gray-200'); + expect(mockSetDefaultViewEmpleados).not.toHaveBeenCalled(); + }); + }); }) diff --git a/ui/src/views/planillas/PlanillasIndex.vue b/ui/src/views/planillas/PlanillasIndex.vue index 8240899..9e76a88 100644 --- a/ui/src/views/planillas/PlanillasIndex.vue +++ b/ui/src/views/planillas/PlanillasIndex.vue @@ -7,7 +7,25 @@ - + +
+ + +
Cargando planillas... @@ -101,7 +119,13 @@ const handleEditPlanilla = (planillaId) => { router.push({ name: 'planillas-edit', params: { id: planillaId } }); }; -// Removed btnClass as manual toggle buttons are removed +const btnViewClass = (viewType) => { + const base = 'p-2 rounded-md transition-colors duration-150 ease-in-out'; + if (currentView.value === viewType) { + return `${base} bg-[var(--accent-color-planillas)] text-white shadow-lg`; + } + return `${base} bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600`; +}; diff --git a/ui/src/views/planillas/__tests__/PlanillasIndex.spec.js b/ui/src/views/planillas/__tests__/PlanillasIndex.spec.js index eea69b5..b168fbb 100644 --- a/ui/src/views/planillas/__tests__/PlanillasIndex.spec.js +++ b/ui/src/views/planillas/__tests__/PlanillasIndex.spec.js @@ -125,4 +125,79 @@ describe('PlanillasIndex.vue', () => { expect(wrapper.text()).toContain('No hay planillas para mostrar'); }); }) + + describe('Local View Toggle Buttons', () => { + it('renders toggle buttons and reflects initial view from store (table)', async () => { + uiStoreMock.defaultViewPlanillas = 'table'; + const wrapper = mountComponent(); + await mockFetchPlanillas(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + + expect(tableViewButton.exists()).toBe(true); + expect(cardViewButton.exists()).toBe(true); + expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-planillas)]'); + expect(cardViewButton.classes()).toContain('bg-gray-200'); + }); + + it('renders toggle buttons and reflects initial view from store (card)', async () => { + uiStoreMock.defaultViewPlanillas = 'card'; + const wrapper = mountComponent(); + await mockFetchPlanillas(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + + expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-planillas)]'); + expect(tableViewButton.classes()).toContain('bg-gray-200'); + }); + + it('switches to card view on button click and updates button styles, does not call global store action', async () => { + uiStoreMock.defaultViewPlanillas = 'table'; + planillasStoreMock.planillas = [{ id: 1, periodo: 'Test' }]; + const wrapper = mountComponent(); + await mockFetchPlanillas(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + + await cardViewButton.trigger('click'); + await wrapper.vm.$nextTick(); + + expect(wrapper.findComponent({ name: 'CardPlanilla' }).exists()).toBe(true); + expect(wrapper.findComponent({ name: 'TablaPlanillas' }).exists()).toBe(false); + expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-planillas)]'); + expect(tableViewButton.classes()).toContain('bg-gray-200'); + expect(mockSetDefaultViewPlanillas).not.toHaveBeenCalled(); + }); + + it('switches back to table view on button click and updates button styles, does not call global store action', async () => { + uiStoreMock.defaultViewPlanillas = 'card'; // Start with card view + planillasStoreMock.planillas = [{ id: 1, periodo: 'Test' }]; + const wrapper = mountComponent(); + await mockFetchPlanillas(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-planillas)]'); + + await tableViewButton.trigger('click'); + await wrapper.vm.$nextTick(); + + expect(wrapper.findComponent({ name: 'TablaPlanillas' }).exists()).toBe(true); + expect(wrapper.findComponent({ name: 'CardPlanilla' }).exists()).toBe(false); + expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-planillas)]'); + expect(cardViewButton.classes()).toContain('bg-gray-200'); + expect(mockSetDefaultViewPlanillas).not.toHaveBeenCalled(); + }); + }); }) diff --git a/ui/src/views/tareas/TareasIndex.vue b/ui/src/views/tareas/TareasIndex.vue index c31127b..4ec5d98 100644 --- a/ui/src/views/tareas/TareasIndex.vue +++ b/ui/src/views/tareas/TareasIndex.vue @@ -7,7 +7,25 @@ - + +
+ + +
Cargando tareas... @@ -95,7 +113,13 @@ const handleEditTarea = (tareaId) => { router.push({ name: 'tareas-edit', params: { id: tareaId } }); }; -// Removed btnClass as manual toggle buttons are removed +const btnViewClass = (viewType) => { + const base = 'p-2 rounded-md transition-colors duration-150 ease-in-out'; + if (currentView.value === viewType) { + return `${base} bg-[var(--accent-color-tareas)] text-white shadow-lg`; + } + return `${base} bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600`; +}; diff --git a/ui/src/views/tareas/__tests__/TareasIndex.spec.js b/ui/src/views/tareas/__tests__/TareasIndex.spec.js index 27bd6e4..2ec8820 100644 --- a/ui/src/views/tareas/__tests__/TareasIndex.spec.js +++ b/ui/src/views/tareas/__tests__/TareasIndex.spec.js @@ -125,4 +125,79 @@ describe('TareasIndex.vue', () => { expect(wrapper.text()).toContain('No hay tareas para mostrar'); }); }) + + describe('Local View Toggle Buttons', () => { + it('renders toggle buttons and reflects initial view from store (table)', async () => { + uiStoreMock.defaultViewTareas = 'table'; + const wrapper = mountComponent(); + await mockFetchTareas(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + + expect(tableViewButton.exists()).toBe(true); + expect(cardViewButton.exists()).toBe(true); + expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-tareas)]'); + expect(cardViewButton.classes()).toContain('bg-gray-200'); + }); + + it('renders toggle buttons and reflects initial view from store (card)', async () => { + uiStoreMock.defaultViewTareas = 'card'; + const wrapper = mountComponent(); + await mockFetchTareas(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + + expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-tareas)]'); + expect(tableViewButton.classes()).toContain('bg-gray-200'); + }); + + it('switches to card view on button click and updates button styles, does not call global store action', async () => { + uiStoreMock.defaultViewTareas = 'table'; + tareasStoreMock.tareas = [{ id: 1, titulo: 'Test' }]; + const wrapper = mountComponent(); + await mockFetchTareas(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + + await cardViewButton.trigger('click'); + await wrapper.vm.$nextTick(); + + expect(wrapper.findComponent({ name: 'CardTarea' }).exists()).toBe(true); + expect(wrapper.findComponent({ name: 'TablaTareas' }).exists()).toBe(false); + expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-tareas)]'); + expect(tableViewButton.classes()).toContain('bg-gray-200'); + expect(mockSetDefaultViewTareas).not.toHaveBeenCalled(); + }); + + it('switches back to table view on button click and updates button styles, does not call global store action', async () => { + uiStoreMock.defaultViewTareas = 'card'; // Start with card view + tareasStoreMock.tareas = [{ id: 1, titulo: 'Test' }]; + const wrapper = mountComponent(); + await mockFetchTareas(); + await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); + + const cardViewButton = wrapper.find('button[aria-label="Card View"]'); + const tableViewButton = wrapper.find('button[aria-label="Table View"]'); + expect(cardViewButton.classes()).toContain('bg-[var(--accent-color-tareas)]'); + + await tableViewButton.trigger('click'); + await wrapper.vm.$nextTick(); + + expect(wrapper.findComponent({ name: 'TablaTareas' }).exists()).toBe(true); + expect(wrapper.findComponent({ name: 'CardTarea' }).exists()).toBe(false); + expect(tableViewButton.classes()).toContain('bg-[var(--accent-color-tareas)]'); + expect(cardViewButton.classes()).toContain('bg-gray-200'); + expect(mockSetDefaultViewTareas).not.toHaveBeenCalled(); + }); + }); })