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(); + }); + }); })