From a70da7630d807a61c4e263d21b4994feb1dd2273 Mon Sep 17 00:00:00 2001 From: jonathan Date: Tue, 15 Sep 2026 16:44:13 -0300 Subject: [PATCH] =?UTF-8?q?feat(course-list):=20agrega=20acci=C3=B3n=20per?= =?UTF-8?q?sonalizada=20al=20seleccionar=20cursos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/Organisms/CourseList.tsx | 23 +++++++++ src/organisms/CourseList/Boxes/BoxImage.tsx | 34 +++++++++++-- .../CourseList/Boxes/BoxTraditional.tsx | 29 ++++++++--- src/organisms/CourseList/CourseList.test.tsx | 50 +++++++++++++++++++ src/organisms/CourseList/types.d.ts | 10 +++- 5 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 src/organisms/CourseList/CourseList.test.tsx diff --git a/src/documentation/pages/Organisms/CourseList.tsx b/src/documentation/pages/Organisms/CourseList.tsx index 92b0d33b3..3f576d370 100644 --- a/src/documentation/pages/Organisms/CourseList.tsx +++ b/src/documentation/pages/Organisms/CourseList.tsx @@ -3,6 +3,11 @@ import { CourseList } from '@/organisms' import { dataFake } from '@/organisms/CourseList/utils' export const ViewCourseList = (): JSX.Element => { + const courseWithCustomClick = { + ...dataFake[18], + onClick: (selectedCourse: typeof dataFake[number]) => console.log(selectedCourse), + } + return ( <> CourseList @@ -21,6 +26,24 @@ export const ViewCourseList = (): JSX.Element => { /> + Acción personalizada al seleccionar una caja + + Cada curso puede incluir onClick para ejecutar una acción personalizada al + seleccionar su caja. Cuando se define, recibe el objeto completo del curso y reemplaza la + redirección configurada en action.href. + + { + console.log(selectedCourse) + }, + }, +]`} + /> + + Tipos de Caja curso Actualmente existen tres formatos en que se muestran las cajas. El tipo que se define es a diff --git a/src/organisms/CourseList/Boxes/BoxImage.tsx b/src/organisms/CourseList/Boxes/BoxImage.tsx index 984a76cba..3530c2a01 100644 --- a/src/organisms/CourseList/Boxes/BoxImage.tsx +++ b/src/organisms/CourseList/Boxes/BoxImage.tsx @@ -1,3 +1,4 @@ +import * as React from 'react' import { Box, LinkBox, LinkOverlay } from '@chakra-ui/react' import { vars } from '@theme' @@ -29,12 +30,29 @@ export function BoxImage({ size = 'large', m, }: ImageBoxProps): JSX.Element { + const hasCustomClick = data?.onClick !== undefined + const isClickable = + hasCustomClick || isCourseActive(data?.action?.enabled ?? false, data?.Profile?.id) + const hasHref = !hasCustomClick && !!data?.action?.href + + const handleClick = (event: React.MouseEvent): void => { + event.preventDefault() + data?.onClick?.(data) + } + + const handleKeyDown = (event: React.KeyboardEvent): void => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + data?.onClick?.(data) + } + } + const boxHeight = { large: '286px', small: '197px', } return ( - + - {!data?.hasFinanzeFreezed && - isCourseActive(data?.action?.enabled ?? false, data?.Profile?.id) && ( + {(hasCustomClick || !data?.hasFinanzeFreezed) && + isClickable && + (hasHref || hasCustomClick) && ( )} diff --git a/src/organisms/CourseList/Boxes/BoxTraditional.tsx b/src/organisms/CourseList/Boxes/BoxTraditional.tsx index 1fc37a6b4..e8088be94 100644 --- a/src/organisms/CourseList/Boxes/BoxTraditional.tsx +++ b/src/organisms/CourseList/Boxes/BoxTraditional.tsx @@ -23,8 +23,22 @@ interface IBoxTraditional { } export function BoxTraditional({ data, modalPaymentText }: IBoxTraditional): JSX.Element { - const isClickable = isCourseActive(data.action?.enabled ?? false, data.Profile?.id) - const hasHref = !!data.action?.href + const hasCustomClick = data.onClick !== undefined + const isClickable = + hasCustomClick || isCourseActive(data.action?.enabled ?? false, data.Profile?.id) + const hasHref = !hasCustomClick && !!data.action?.href + + const handleClick = (event: React.MouseEvent): void => { + event.preventDefault() + data.onClick?.(data) + } + + const handleKeyDown = (event: React.KeyboardEvent): void => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + data.onClick?.(data) + } + } useEnterNavigate() @@ -47,18 +61,21 @@ export function BoxTraditional({ data, modalPaymentText }: IBoxTraditional): JSX boxShadow: `0 0 0 3px ${vars('colors-alert-deepSkyBlue')} inset`, }} tabIndex={0} - role={hasHref ? 'link' : undefined} + role={hasHref ? 'link' : hasCustomClick ? 'button' : undefined} data-href={hasHref ? data.action?.href : undefined} + onKeyDown={hasCustomClick ? handleKeyDown : undefined} > - {isClickable && hasHref && ( + {isClickable && (hasHref || hasCustomClick) && ( )} diff --git a/src/organisms/CourseList/CourseList.test.tsx b/src/organisms/CourseList/CourseList.test.tsx new file mode 100644 index 000000000..7349f5f12 --- /dev/null +++ b/src/organisms/CourseList/CourseList.test.tsx @@ -0,0 +1,50 @@ +import { ChakraProvider } from '@chakra-ui/react' +import { fireEvent, render, screen } from '@testing-library/react' + +import { CourseList } from './CourseList' +import { ExtendAcademicList, WrapperCoursesProps } from './types' +import { dataFake } from './utils' + +const course = dataFake[0] as ExtendAcademicList + +const renderCourseList = ( + courseData: ExtendAcademicList, + typeBox: WrapperCoursesProps['typeBox'] = 'TRADITIONAL' +): ReturnType => + render( + + + + ) + +describe.each(['TRADITIONAL', 'IMAGE_LARGE'] as const)('CourseList %s', (typeBox) => { + it('calls the data onClick from the overlay with the full course and does not redirect', () => { + const onClick = jest.fn() + const courseWithOnClick = { ...course, onClick } + renderCourseList(courseWithOnClick, typeBox) + + const overlay = screen.getByTestId('course-link-overlay') + + expect(screen.queryByRole('link')).not.toBeInTheDocument() + expect(overlay).not.toHaveAttribute('href') + expect(fireEvent.click(overlay)).toBe(false) + expect(onClick).toHaveBeenCalledTimes(1) + expect(onClick).toHaveBeenCalledWith(courseWithOnClick) + }) + + it('keeps the redirect href when the course does not provide onClick', () => { + renderCourseList(course, typeBox) + + expect(screen.getByTestId('course-link-overlay')).toHaveAttribute('href', course.action?.href) + }) + + it('calls the custom action when the selectable box is activated with Enter', () => { + const onClick = jest.fn() + const courseWithOnClick = { ...course, onClick } + renderCourseList(courseWithOnClick, typeBox) + + fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' }) + + expect(onClick).toHaveBeenCalledWith(courseWithOnClick) + }) +}) diff --git a/src/organisms/CourseList/types.d.ts b/src/organisms/CourseList/types.d.ts index 78eb210bf..79b2aa61b 100644 --- a/src/organisms/CourseList/types.d.ts +++ b/src/organisms/CourseList/types.d.ts @@ -1,12 +1,20 @@ import { AcademicBox } from '@eclass/api' -export type ExtendAcademicList = AcademicBox & { +export type CourseClickPayload = AcademicBox & { soonCourse?: { show?: true text?: string } } +export type ExtendAcademicList = CourseClickPayload & { + /** + * Ejecuta una acción personalizada al seleccionar la caja en lugar de navegar a `action.href`. + * Recibe el objeto completo que se utilizó para renderizar la caja. + */ + onClick?: (course: CourseClickPayload) => void +} + interface PaymentText { title: string body: string