Loading backend @ c27fc035 Compare aac3fc30 to c27fc035 Original line number Diff line number Diff line Subproject commit aac3fc3002ca4ce278e76dc2baa6d6f8b5f1cc73 Subproject commit c27fc03543594163601281b728067593ffa4815d frontend/src/instructor/TeamsScores/TeamScoreCard.tsx +40 −29 Original line number Diff line number Diff line import { Card, Classes, Colors, Tooltip } from '@blueprintjs/core' import { Button, Card, Classes, Colors, Tooltip } from '@blueprintjs/core' import { css, cx } from '@emotion/css' import { TeamLabel } from '@inject/frontend' import type { Team, TeamLearningObjective } from '@inject/graphql' Loading @@ -6,18 +6,6 @@ import { TickOrCross } from '@inject/shared' import type { FC } from 'react' import { LabeledProgressBar } from '../../components/LabeledProgressBar' const wrapper = css` width: 12rem; display: flex; flex-direction: column; align-items: center; gap: 0.5rem; ` const fillClass = css` width: 100%; ` const progressRow = css` width: 100%; display: flex; Loading @@ -27,9 +15,7 @@ const progressRow = css` const progressItem = css` width: 100%; flex: 1; min-width: 0; display: flex; align-items: flex-start; span { Loading @@ -43,6 +29,17 @@ const todoText = css` line-height: 1.1; ` const statsRow = css` width: 100%; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.5rem; ` const statItem = css` text-align: center; ` const objectivesTooltip = css` display: flex; flex-direction: column; Loading Loading @@ -78,14 +75,20 @@ type TeamScoreCardProps = { team: Team teamLearningObjectives: TeamLearningObjective[] teamTodos: number teamComments: number row?: boolean isExpanded?: boolean onClick?: () => void } export const TeamScoreCard: FC<TeamScoreCardProps> = ({ team, teamLearningObjectives, teamTodos, teamComments, row, isExpanded = false, onClick, }) => { const reachedObjectives = teamLearningObjectives.filter( obj => obj.reached Loading @@ -107,6 +110,12 @@ export const TeamScoreCard: FC<TeamScoreCardProps> = ({ border-color: ${Colors.ORANGE5} !important; ` : ''} ${isExpanded ? ` border: 1px solid !important; border-color: ${Colors.BLUE5} !important; ` : ''} `} > <div Loading @@ -130,6 +139,16 @@ export const TeamScoreCard: FC<TeamScoreCardProps> = ({ indexName={team.openSearchAccess?.indexName} /> </div> {onClick && ( <Button minimal icon={isExpanded ? 'chevron-up' : 'chevron-down'} aria-label={ isExpanded ? 'Collapse team details' : 'Expand team details' } onClick={onClick} /> )} </div> <div className={progressRow}> <div className={progressItem}> Loading Loading @@ -188,20 +207,8 @@ export const TeamScoreCard: FC<TeamScoreCardProps> = ({ </Tooltip> </div> </div> <div className={cx({ [wrapper]: true, [fillClass]: true, })} > <span className={cx( css` white-space: nowrap; `, todoText )} > <div className={cx(todoText, statsRow)}> <span className={statItem}> <span className={Classes.TEXT_MUTED}>To-do: </span> <span className={ Loading @@ -215,6 +222,10 @@ export const TeamScoreCard: FC<TeamScoreCardProps> = ({ {teamTodos} </span> </span> <span className={statItem}> <span className={Classes.TEXT_MUTED}>Comments: </span> <span>{teamComments}</span> </span> </div> </Card> ) Loading frontend/src/instructor/TeamsScores/index.tsx +264 −18 Original line number Diff line number Diff line import { NonIdealState } from '@blueprintjs/core' import { Button, ButtonGroup, Colors, NonIdealState, SectionCard, Tab, Tabs, } from '@blueprintjs/core' import { css } from '@emotion/css' import { ExerciseInstructorComments, type ResultOf, type Team, TeamLearningObjectivesQuery, type TodoLogActionLogsQuery, useTypedQuery, } from '@inject/graphql' import { useMemo } from 'react' import { useTranslationFrontend } from '@inject/locale' import { useEffect, useMemo, useRef, useState } from 'react' // import { InstructorComments } from '../../components' import { iTodoLogActionLogCheck } from '../../utils' // import { InstructorTodoLog } from '../InstructorTodoLog' import { TeamScoreCard } from './TeamScoreCard' interface TodoTabsProps { exerciseId: string teams: Team[] actionLogData?: ResultOf<typeof TodoLogActionLogsQuery> } const TeamsScores = ({ teams, actionLogData }: TodoTabsProps) => { const { t } = useTranslationFrontend() const [activeTab, setActiveTab] = useState<'todos' | 'instructor-comments'>( 'todos' ) const [expandedTeamId, setExpandedTeamId] = useState<Team['id'] | null>(null) const [overlayTop, setOverlayTop] = useState(0) const [overlayMaxHeight, setOverlayMaxHeight] = useState(0) const [done, setDone] = useState(false) const cardRefs = useRef<Record<string, HTMLDivElement | null>>({}) const teamIds = teams.map(team => team.id) const [{ data }] = useTypedQuery({ query: TeamLearningObjectivesQuery, Loading @@ -24,6 +46,51 @@ const TeamsScores = ({ teams, actionLogData }: TodoTabsProps) => { pause: !teamIds.length, context: useMemo(() => ({ suspense: true }), []), }) const [{ data: instructorCommentsData }] = useTypedQuery({ query: ExerciseInstructorComments, variables: { teamIds, }, }) const todoActionLogs = actionLogData?.teamActionLogs .filter(iTodoLogActionLogCheck) .filter(log => log.done === done) || [] // const selectedActionLogs = // expandedTeamId === null // ? [] // : todoActionLogs.filter(log => log.teamId === expandedTeamId) // const overlayContentMaxHeight = Math.max(0, overlayMaxHeight - 56) useEffect(() => { setActiveTab('todos') }, [expandedTeamId]) useEffect(() => { const updateOverlayPosition = () => { if (expandedTeamId === null) return const selectedCard = cardRefs.current[String(expandedTeamId)] if (!selectedCard) return setOverlayTop(selectedCard.offsetTop + selectedCard.offsetHeight + 4) const selectedCardRect = selectedCard.getBoundingClientRect() const availableViewportSpace = window.innerHeight - selectedCardRect.bottom - 16 setOverlayMaxHeight(Math.max(0, availableViewportSpace)) } updateOverlayPosition() window.addEventListener('resize', updateOverlayPosition) window.addEventListener('scroll', updateOverlayPosition, { passive: true }) return () => { window.removeEventListener('resize', updateOverlayPosition) window.removeEventListener('scroll', updateOverlayPosition) } }, [expandedTeamId, teams.length]) if (teams.length === 0) { return ( Loading @@ -40,30 +107,209 @@ const TeamsScores = ({ teams, actionLogData }: TodoTabsProps) => { ) } return ( <div className={css` display: flex; flex-direction: column; gap: 0.35rem; `} > <div className={css` display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; `} > <span className={css` font-size: 1.2em; font-weight: 700; `} > {t('overview.teams')}: </span> <div className={css` display: flex; align-items: center; gap: 0.5rem; `} > <span className={css` font-size: 1.2em; font-weight: 700; `} > To-do list: </span> <Tabs selectedTabId={done ? 'done' : 'notdone'} onChange={newTabId => setDone(newTabId === 'done')} > <Tab title={t('overview.todoList.done')} id='done' /> <Tab title={t('overview.todoList.notDone')} id='notdone' /> </Tabs> </div> </div> <div className={css` position: relative; `} > <div className={css` display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 0.25rem; @media (max-width: 1650px) { grid-template-columns: repeat(4, minmax(0, 1fr)); } @media (max-width: 1400px) { grid-template-columns: repeat(3, minmax(0, 1fr)); } @media (max-width: 1300px) { grid-template-columns: repeat(2, minmax(0, 1fr)); } @media (max-width: 800px) { grid-template-columns: minmax(0, 1fr); } `} > {teams.map(team => ( <div key={team.id}> <TeamScoreCard team={team} teamLearningObjectives={ {teams.map(team => { const teamLearningObjectives = data?.teamLearningObjectives.filter(teamObjective => teamObjective.teamIds.includes(team.id) ) || [] const teamTodos = todoActionLogs.filter(log => log.teamId === team.id).length || 0 const teamComments = instructorCommentsData?.exerciseInstructorComments?.filter( comment => comment.teamId === team.id ).length || 0 const isExpanded = expandedTeamId === team.id return ( <div key={team.id} ref={element => { cardRefs.current[String(team.id)] = element }} > <TeamScoreCard team={team} teamLearningObjectives={teamLearningObjectives} teamTodos={teamTodos} teamComments={teamComments} isExpanded={isExpanded} onClick={() => setExpandedTeamId(current => current === team.id ? null : team.id ) } teamTodos={ actionLogData?.teamActionLogs .filter(iTodoLogActionLogCheck) .filter(log => log.teamId === team.id).length || 0 /> </div> ) })} </div> {expandedTeamId && ( <SectionCard className={css` position: absolute; top: ${overlayTop}px; left: 0; right: 0; z-index: 20; max-height: ${overlayMaxHeight}px; overflow: hidden; background-color: ${Colors.DARK_GRAY5} !important; opacity: 1 !important; font-size: 0.9rem; line-height: 1.35; border: 1px solid ${Colors.GRAY3} !important; `} > <div className={css` display: flex; flex-direction: column; gap: 0.5rem; max-height: ${overlayMaxHeight}px; `} > <div className={css` display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; `} > <ButtonGroup className={css` gap: 0.25rem; `} > <Button text='Todos' icon='book' active={activeTab === 'todos'} onClick={() => setActiveTab('todos')} /> <Button text='Instructor comments' icon='comment' active={activeTab === 'instructor-comments'} onClick={() => setActiveTab('instructor-comments')} /> </ButtonGroup> <Button minimal icon='cross' aria-label='Close' onClick={() => setExpandedTeamId(null)} /> </div> {/* <div className={css` min-height: 0; max-height: ${overlayContentMaxHeight}px; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; `} > {activeTab === 'todos' ? ( <InstructorTodoLog actionLogs={selectedActionLogs} contextType='team' done={done} teamIds={[expandedTeamId]} /> ) : ( <InstructorComments exerciseId={exerciseId} instructorComments={ instructorCommentsData?.exerciseInstructorComments?.filter( comment => comment.teamId === expandedTeamId ) || [] } loading={loading} /> )} </div> */} </div> </SectionCard> )} </div> ))} </div> ) } Loading frontend/src/routes/_protected/instructor/$exerciseId/index.tsx +3 −20 Original line number Diff line number Diff line import { Divider } from '@blueprintjs/core' import { css } from '@emotion/css' import { TodoLogActionLogsQuery, useTypedQuery } from '@inject/graphql' import { useTranslationFrontend } from '@inject/locale' import { createFileRoute } from '@tanstack/react-router' import { useMemo } from 'react' import { InstructorComments } from '../../../../components' import { useSubscribedTeams } from '../../../../instructor/InstructorTeamSelector/useSubscribedTeams' import { TodoTabs } from '../../../../instructor/InstructorTodoLog/TodoTabs' import TeamsScores from '../../../../instructor/TeamsScores' // TODO: improve this layout, use components from analyst overview const RouteComponent = () => { const { exerciseId } = InstructorLandingPageRoute.useParams() const { t } = useTranslationFrontend() const selectedTeamStates = useSubscribedTeams({ exerciseId, Loading Loading @@ -40,22 +35,10 @@ const RouteComponent = () => { padding: 0.5rem; `} > <div> <b style={{ fontSize: '1.2em' }}>{t('overview.teams')}:</b> <TeamsScores teams={selectedTeamStates} actionLogData={actionLogData} /> </div> <Divider /> <TodoTabs teamIds={selectedTeamStates.map(team => team.id)} actionLogData={actionLogData} /> <Divider /> <InstructorComments <TeamsScores exerciseId={exerciseId} teamIds={selectedTeamStates.map(team => team.id)} teams={selectedTeamStates} actionLogData={actionLogData} /> </main> ) Loading Loading
backend @ c27fc035 Compare aac3fc30 to c27fc035 Original line number Diff line number Diff line Subproject commit aac3fc3002ca4ce278e76dc2baa6d6f8b5f1cc73 Subproject commit c27fc03543594163601281b728067593ffa4815d
frontend/src/instructor/TeamsScores/TeamScoreCard.tsx +40 −29 Original line number Diff line number Diff line import { Card, Classes, Colors, Tooltip } from '@blueprintjs/core' import { Button, Card, Classes, Colors, Tooltip } from '@blueprintjs/core' import { css, cx } from '@emotion/css' import { TeamLabel } from '@inject/frontend' import type { Team, TeamLearningObjective } from '@inject/graphql' Loading @@ -6,18 +6,6 @@ import { TickOrCross } from '@inject/shared' import type { FC } from 'react' import { LabeledProgressBar } from '../../components/LabeledProgressBar' const wrapper = css` width: 12rem; display: flex; flex-direction: column; align-items: center; gap: 0.5rem; ` const fillClass = css` width: 100%; ` const progressRow = css` width: 100%; display: flex; Loading @@ -27,9 +15,7 @@ const progressRow = css` const progressItem = css` width: 100%; flex: 1; min-width: 0; display: flex; align-items: flex-start; span { Loading @@ -43,6 +29,17 @@ const todoText = css` line-height: 1.1; ` const statsRow = css` width: 100%; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.5rem; ` const statItem = css` text-align: center; ` const objectivesTooltip = css` display: flex; flex-direction: column; Loading Loading @@ -78,14 +75,20 @@ type TeamScoreCardProps = { team: Team teamLearningObjectives: TeamLearningObjective[] teamTodos: number teamComments: number row?: boolean isExpanded?: boolean onClick?: () => void } export const TeamScoreCard: FC<TeamScoreCardProps> = ({ team, teamLearningObjectives, teamTodos, teamComments, row, isExpanded = false, onClick, }) => { const reachedObjectives = teamLearningObjectives.filter( obj => obj.reached Loading @@ -107,6 +110,12 @@ export const TeamScoreCard: FC<TeamScoreCardProps> = ({ border-color: ${Colors.ORANGE5} !important; ` : ''} ${isExpanded ? ` border: 1px solid !important; border-color: ${Colors.BLUE5} !important; ` : ''} `} > <div Loading @@ -130,6 +139,16 @@ export const TeamScoreCard: FC<TeamScoreCardProps> = ({ indexName={team.openSearchAccess?.indexName} /> </div> {onClick && ( <Button minimal icon={isExpanded ? 'chevron-up' : 'chevron-down'} aria-label={ isExpanded ? 'Collapse team details' : 'Expand team details' } onClick={onClick} /> )} </div> <div className={progressRow}> <div className={progressItem}> Loading Loading @@ -188,20 +207,8 @@ export const TeamScoreCard: FC<TeamScoreCardProps> = ({ </Tooltip> </div> </div> <div className={cx({ [wrapper]: true, [fillClass]: true, })} > <span className={cx( css` white-space: nowrap; `, todoText )} > <div className={cx(todoText, statsRow)}> <span className={statItem}> <span className={Classes.TEXT_MUTED}>To-do: </span> <span className={ Loading @@ -215,6 +222,10 @@ export const TeamScoreCard: FC<TeamScoreCardProps> = ({ {teamTodos} </span> </span> <span className={statItem}> <span className={Classes.TEXT_MUTED}>Comments: </span> <span>{teamComments}</span> </span> </div> </Card> ) Loading
frontend/src/instructor/TeamsScores/index.tsx +264 −18 Original line number Diff line number Diff line import { NonIdealState } from '@blueprintjs/core' import { Button, ButtonGroup, Colors, NonIdealState, SectionCard, Tab, Tabs, } from '@blueprintjs/core' import { css } from '@emotion/css' import { ExerciseInstructorComments, type ResultOf, type Team, TeamLearningObjectivesQuery, type TodoLogActionLogsQuery, useTypedQuery, } from '@inject/graphql' import { useMemo } from 'react' import { useTranslationFrontend } from '@inject/locale' import { useEffect, useMemo, useRef, useState } from 'react' // import { InstructorComments } from '../../components' import { iTodoLogActionLogCheck } from '../../utils' // import { InstructorTodoLog } from '../InstructorTodoLog' import { TeamScoreCard } from './TeamScoreCard' interface TodoTabsProps { exerciseId: string teams: Team[] actionLogData?: ResultOf<typeof TodoLogActionLogsQuery> } const TeamsScores = ({ teams, actionLogData }: TodoTabsProps) => { const { t } = useTranslationFrontend() const [activeTab, setActiveTab] = useState<'todos' | 'instructor-comments'>( 'todos' ) const [expandedTeamId, setExpandedTeamId] = useState<Team['id'] | null>(null) const [overlayTop, setOverlayTop] = useState(0) const [overlayMaxHeight, setOverlayMaxHeight] = useState(0) const [done, setDone] = useState(false) const cardRefs = useRef<Record<string, HTMLDivElement | null>>({}) const teamIds = teams.map(team => team.id) const [{ data }] = useTypedQuery({ query: TeamLearningObjectivesQuery, Loading @@ -24,6 +46,51 @@ const TeamsScores = ({ teams, actionLogData }: TodoTabsProps) => { pause: !teamIds.length, context: useMemo(() => ({ suspense: true }), []), }) const [{ data: instructorCommentsData }] = useTypedQuery({ query: ExerciseInstructorComments, variables: { teamIds, }, }) const todoActionLogs = actionLogData?.teamActionLogs .filter(iTodoLogActionLogCheck) .filter(log => log.done === done) || [] // const selectedActionLogs = // expandedTeamId === null // ? [] // : todoActionLogs.filter(log => log.teamId === expandedTeamId) // const overlayContentMaxHeight = Math.max(0, overlayMaxHeight - 56) useEffect(() => { setActiveTab('todos') }, [expandedTeamId]) useEffect(() => { const updateOverlayPosition = () => { if (expandedTeamId === null) return const selectedCard = cardRefs.current[String(expandedTeamId)] if (!selectedCard) return setOverlayTop(selectedCard.offsetTop + selectedCard.offsetHeight + 4) const selectedCardRect = selectedCard.getBoundingClientRect() const availableViewportSpace = window.innerHeight - selectedCardRect.bottom - 16 setOverlayMaxHeight(Math.max(0, availableViewportSpace)) } updateOverlayPosition() window.addEventListener('resize', updateOverlayPosition) window.addEventListener('scroll', updateOverlayPosition, { passive: true }) return () => { window.removeEventListener('resize', updateOverlayPosition) window.removeEventListener('scroll', updateOverlayPosition) } }, [expandedTeamId, teams.length]) if (teams.length === 0) { return ( Loading @@ -40,30 +107,209 @@ const TeamsScores = ({ teams, actionLogData }: TodoTabsProps) => { ) } return ( <div className={css` display: flex; flex-direction: column; gap: 0.35rem; `} > <div className={css` display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; `} > <span className={css` font-size: 1.2em; font-weight: 700; `} > {t('overview.teams')}: </span> <div className={css` display: flex; align-items: center; gap: 0.5rem; `} > <span className={css` font-size: 1.2em; font-weight: 700; `} > To-do list: </span> <Tabs selectedTabId={done ? 'done' : 'notdone'} onChange={newTabId => setDone(newTabId === 'done')} > <Tab title={t('overview.todoList.done')} id='done' /> <Tab title={t('overview.todoList.notDone')} id='notdone' /> </Tabs> </div> </div> <div className={css` position: relative; `} > <div className={css` display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 0.25rem; @media (max-width: 1650px) { grid-template-columns: repeat(4, minmax(0, 1fr)); } @media (max-width: 1400px) { grid-template-columns: repeat(3, minmax(0, 1fr)); } @media (max-width: 1300px) { grid-template-columns: repeat(2, minmax(0, 1fr)); } @media (max-width: 800px) { grid-template-columns: minmax(0, 1fr); } `} > {teams.map(team => ( <div key={team.id}> <TeamScoreCard team={team} teamLearningObjectives={ {teams.map(team => { const teamLearningObjectives = data?.teamLearningObjectives.filter(teamObjective => teamObjective.teamIds.includes(team.id) ) || [] const teamTodos = todoActionLogs.filter(log => log.teamId === team.id).length || 0 const teamComments = instructorCommentsData?.exerciseInstructorComments?.filter( comment => comment.teamId === team.id ).length || 0 const isExpanded = expandedTeamId === team.id return ( <div key={team.id} ref={element => { cardRefs.current[String(team.id)] = element }} > <TeamScoreCard team={team} teamLearningObjectives={teamLearningObjectives} teamTodos={teamTodos} teamComments={teamComments} isExpanded={isExpanded} onClick={() => setExpandedTeamId(current => current === team.id ? null : team.id ) } teamTodos={ actionLogData?.teamActionLogs .filter(iTodoLogActionLogCheck) .filter(log => log.teamId === team.id).length || 0 /> </div> ) })} </div> {expandedTeamId && ( <SectionCard className={css` position: absolute; top: ${overlayTop}px; left: 0; right: 0; z-index: 20; max-height: ${overlayMaxHeight}px; overflow: hidden; background-color: ${Colors.DARK_GRAY5} !important; opacity: 1 !important; font-size: 0.9rem; line-height: 1.35; border: 1px solid ${Colors.GRAY3} !important; `} > <div className={css` display: flex; flex-direction: column; gap: 0.5rem; max-height: ${overlayMaxHeight}px; `} > <div className={css` display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; `} > <ButtonGroup className={css` gap: 0.25rem; `} > <Button text='Todos' icon='book' active={activeTab === 'todos'} onClick={() => setActiveTab('todos')} /> <Button text='Instructor comments' icon='comment' active={activeTab === 'instructor-comments'} onClick={() => setActiveTab('instructor-comments')} /> </ButtonGroup> <Button minimal icon='cross' aria-label='Close' onClick={() => setExpandedTeamId(null)} /> </div> {/* <div className={css` min-height: 0; max-height: ${overlayContentMaxHeight}px; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; `} > {activeTab === 'todos' ? ( <InstructorTodoLog actionLogs={selectedActionLogs} contextType='team' done={done} teamIds={[expandedTeamId]} /> ) : ( <InstructorComments exerciseId={exerciseId} instructorComments={ instructorCommentsData?.exerciseInstructorComments?.filter( comment => comment.teamId === expandedTeamId ) || [] } loading={loading} /> )} </div> */} </div> </SectionCard> )} </div> ))} </div> ) } Loading
frontend/src/routes/_protected/instructor/$exerciseId/index.tsx +3 −20 Original line number Diff line number Diff line import { Divider } from '@blueprintjs/core' import { css } from '@emotion/css' import { TodoLogActionLogsQuery, useTypedQuery } from '@inject/graphql' import { useTranslationFrontend } from '@inject/locale' import { createFileRoute } from '@tanstack/react-router' import { useMemo } from 'react' import { InstructorComments } from '../../../../components' import { useSubscribedTeams } from '../../../../instructor/InstructorTeamSelector/useSubscribedTeams' import { TodoTabs } from '../../../../instructor/InstructorTodoLog/TodoTabs' import TeamsScores from '../../../../instructor/TeamsScores' // TODO: improve this layout, use components from analyst overview const RouteComponent = () => { const { exerciseId } = InstructorLandingPageRoute.useParams() const { t } = useTranslationFrontend() const selectedTeamStates = useSubscribedTeams({ exerciseId, Loading Loading @@ -40,22 +35,10 @@ const RouteComponent = () => { padding: 0.5rem; `} > <div> <b style={{ fontSize: '1.2em' }}>{t('overview.teams')}:</b> <TeamsScores teams={selectedTeamStates} actionLogData={actionLogData} /> </div> <Divider /> <TodoTabs teamIds={selectedTeamStates.map(team => team.id)} actionLogData={actionLogData} /> <Divider /> <InstructorComments <TeamsScores exerciseId={exerciseId} teamIds={selectedTeamStates.map(team => team.id)} teams={selectedTeamStates} actionLogData={actionLogData} /> </main> ) Loading