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