Loading frontend/src/instructor/InstructorTodoLog/EmailRecipientsTag.tsx 0 → 100644 +38 −0 Original line number Diff line number Diff line import { Tag } from '@blueprintjs/core' import { css, cx } from '@emotion/css' import type { ITodoLogActionLog } from '@inject/graphql' import { useTranslationFrontend } from '@inject/locale' import type { FC } from 'react' export const EmailRecipientsTag: FC<{ actionLog: ITodoLogActionLog className?: string }> = ({ actionLog, className }) => { const { t } = useTranslationFrontend() if (actionLog.details.__typename !== 'IEmailType') { return null } const { sender } = actionLog.details const recipients = actionLog.details.thread.participants.filter( participant => participant.address !== sender.address ) return ( <Tag className={cx( css` margin-right: 0.25rem; display: inline-flex; align-items: center; vertical-align: middle; `, className )} round icon='envelope' > {t('overview.todoList.email.recipients', { count: recipients.length })} </Tag> ) } frontend/src/instructor/InstructorTodoLog/LogItem.tsx +60 −42 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ import { useTypedMutation, useTypedQuery, } from '@inject/graphql' import { useTranslationFrontend } from '@inject/locale' import type { LinkButtonProps } from '@inject/shared' import { EmailSelection, LinkButton, Timestamp } from '@inject/shared' import type { NavigateOptions } from '@tanstack/react-router' Loading @@ -19,8 +20,46 @@ import { InstructorToolChannelActionLogRoute } from '../../routes/_protected/ins import { InstructorEmailThreadPageRoute } from '../../routes/_protected/instructor/$exerciseId/$teamId/email/$tab/$threadId' import { canBeReviewed, getIcon } from '../../utils' import { QuestionnaireStatus } from '../InstructorQuestionnaire/QuestionnaireStatus' import { EmailRecipientsTag } from './EmailRecipientsTag' import { OverviewPillNav } from './OverviewPillNav' const recipientRow = css` display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.25rem 0.5rem; align-items: center; padding: 0.2rem 0.35rem; border-radius: 3px; background: rgba(255, 255, 255, 0.03); ` const recipientAddress = css` font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; ` const recipientMeta = css` display: inline-flex; gap: 0.25rem; flex-wrap: wrap; justify-content: flex-end; ` const recipientDescription = css` grid-column: 1 / -1; font-size: 12px; ` const topMetaRow = css` display: flex; align-items: center; gap: 0.3rem; flex-wrap: wrap; ` const getTitle = (actionLog: ITodoLogActionLog): string => { switch (actionLog.details.__typename) { case 'IEmailType': Loading Loading @@ -251,40 +290,11 @@ const recipientList = css` } ` const recipientRow = css` display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.25rem 0.5rem; align-items: center; padding: 0.2rem 0.35rem; border-radius: 3px; background: rgba(255, 255, 255, 0.03); ` const recipientAddress = css` font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; ` const recipientMeta = css` display: inline-flex; gap: 0.25rem; flex-wrap: wrap; justify-content: flex-end; ` const recipientDescription = css` grid-column: 1 / -1; font-size: 12px; ` const EmailLogItemContent: FC<{ actionLog: ITodoLogActionLog milestoneStates: MilestoneState[] }> = ({ actionLog, milestoneStates }) => { const { t } = useTranslationFrontend() if (actionLog.details.__typename !== 'IEmailType') { return null } Loading @@ -306,12 +316,17 @@ const EmailLogItemContent: FC<{ <div key={recipient.address} className={recipientRow}> <div className={recipientAddress}>{recipient.address}</div> <div className={recipientMeta}> <Tag minimal>{`Templates: ${recipient.templateCount}`}</Tag> <Tag minimal>{`${t('overview.todoList.email.templates', { count: recipient.templateCount, })}`}</Tag> {recipient.totalMilestonesCount !== undefined && ( <Tag minimal intent='primary' >{`Milestones: ${recipient.pendingMilestonesCount}/${recipient.totalMilestonesCount}`}</Tag> <Tag minimal intent='primary'>{`${t( 'overview.todoList.email.milestones', { count: recipient.pendingMilestonesCount, total: recipient.totalMilestonesCount, } )}`}</Tag> )} </div> {recipient.description && ( Loading Loading @@ -450,19 +465,22 @@ const LogItem: FC<LogItemProps> = ({ > {getTitle(actionLog)} </h5> <div className={topMetaRow}> <OverviewPillNav actionLog={actionLog} /> {questionnaireStatus && ( <QuestionnaireStatus teamStateStatus={questionnaireStatus} canBeReviewed={canBeReviewed(actionLog.details)} /> )} <EmailRecipientsTag actionLog={actionLog} /> <Timestamp formatTimestampProps={{ timestamp: actionLog.timestamp, inExerciseTime: actionLog.inExerciseTime, }} /> </div> {questionnaireStatus && ( <QuestionnaireStatus teamStateStatus={questionnaireStatus} canBeReviewed={canBeReviewed(actionLog.details)} /> )} <LogItemContent actionLog={actionLog} milestoneStates={milestoneStates ?? []} Loading frontend/src/instructor/InstructorTodoLog/OverviewPillNav.tsx +12 −8 Original line number Diff line number Diff line import { Tag } from '@blueprintjs/core' import { css } from '@emotion/css' import { css, cx } from '@emotion/css' import type { ITodoLogActionLog } from '@inject/graphql' import { getColorScheme } from '@inject/shared' import { useNavigate } from '@tanstack/react-router' Loading @@ -8,16 +8,20 @@ import { InstructorTeamLandingPageRoute } from '../../routes/_protected/instruct export const OverviewPillNav: FC<{ actionLog: ITodoLogActionLog }> = ({ actionLog }) => { className?: string }> = ({ actionLog, className }) => { const nav = useNavigate() return ( <Tag className={css` className={cx( css` margin-right: 0.25rem; &:hover { cursor: pointer; } `} `, className )} title='Click to enter team overview' onClick={() => { nav({ Loading locale/resources/cs/frontend.json +6 −1 Original line number Diff line number Diff line Loading @@ -514,7 +514,12 @@ "done": "Dokončeno", "notDone": "Nedokončeno", "notFoundTitle": "Žádné injecty", "notFoundDescription": "Nebyly nalezeny žádné injecty, které by mohly být zobrazeny v téhle kategorii. Prosím počkejte na nové" "notFoundDescription": "Nebyly nalezeny žádné injecty, které by mohly být zobrazeny v téhle kategorii. Prosím počkejte na nové", "email": { "templates": "Šablony: {{count}}", "milestones": "Milníky: {{count}}/{{total}}", "recipients": "Příjemci: {{count}}" } }, "instructorComments": { "title": "Komentáře od instruktorů", Loading locale/resources/en/frontend.json +6 −1 Original line number Diff line number Diff line Loading @@ -514,7 +514,12 @@ "done": "Done", "notDone": "Not done", "notFoundTitle": "No inject items", "notFoundDescription": "There are no log items that can be shown in this category. Please await for new injects" "notFoundDescription": "There are no log items that can be shown in this category. Please await for new injects", "email": { "templates": "Templates: {{count}}", "milestones": "Milestones: {{count}}/{{total}}", "recipients": "Recipients: {{count}}" } }, "instructorComments": { "title": "Instructor comments", Loading Loading
frontend/src/instructor/InstructorTodoLog/EmailRecipientsTag.tsx 0 → 100644 +38 −0 Original line number Diff line number Diff line import { Tag } from '@blueprintjs/core' import { css, cx } from '@emotion/css' import type { ITodoLogActionLog } from '@inject/graphql' import { useTranslationFrontend } from '@inject/locale' import type { FC } from 'react' export const EmailRecipientsTag: FC<{ actionLog: ITodoLogActionLog className?: string }> = ({ actionLog, className }) => { const { t } = useTranslationFrontend() if (actionLog.details.__typename !== 'IEmailType') { return null } const { sender } = actionLog.details const recipients = actionLog.details.thread.participants.filter( participant => participant.address !== sender.address ) return ( <Tag className={cx( css` margin-right: 0.25rem; display: inline-flex; align-items: center; vertical-align: middle; `, className )} round icon='envelope' > {t('overview.todoList.email.recipients', { count: recipients.length })} </Tag> ) }
frontend/src/instructor/InstructorTodoLog/LogItem.tsx +60 −42 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ import { useTypedMutation, useTypedQuery, } from '@inject/graphql' import { useTranslationFrontend } from '@inject/locale' import type { LinkButtonProps } from '@inject/shared' import { EmailSelection, LinkButton, Timestamp } from '@inject/shared' import type { NavigateOptions } from '@tanstack/react-router' Loading @@ -19,8 +20,46 @@ import { InstructorToolChannelActionLogRoute } from '../../routes/_protected/ins import { InstructorEmailThreadPageRoute } from '../../routes/_protected/instructor/$exerciseId/$teamId/email/$tab/$threadId' import { canBeReviewed, getIcon } from '../../utils' import { QuestionnaireStatus } from '../InstructorQuestionnaire/QuestionnaireStatus' import { EmailRecipientsTag } from './EmailRecipientsTag' import { OverviewPillNav } from './OverviewPillNav' const recipientRow = css` display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.25rem 0.5rem; align-items: center; padding: 0.2rem 0.35rem; border-radius: 3px; background: rgba(255, 255, 255, 0.03); ` const recipientAddress = css` font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; ` const recipientMeta = css` display: inline-flex; gap: 0.25rem; flex-wrap: wrap; justify-content: flex-end; ` const recipientDescription = css` grid-column: 1 / -1; font-size: 12px; ` const topMetaRow = css` display: flex; align-items: center; gap: 0.3rem; flex-wrap: wrap; ` const getTitle = (actionLog: ITodoLogActionLog): string => { switch (actionLog.details.__typename) { case 'IEmailType': Loading Loading @@ -251,40 +290,11 @@ const recipientList = css` } ` const recipientRow = css` display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.25rem 0.5rem; align-items: center; padding: 0.2rem 0.35rem; border-radius: 3px; background: rgba(255, 255, 255, 0.03); ` const recipientAddress = css` font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; ` const recipientMeta = css` display: inline-flex; gap: 0.25rem; flex-wrap: wrap; justify-content: flex-end; ` const recipientDescription = css` grid-column: 1 / -1; font-size: 12px; ` const EmailLogItemContent: FC<{ actionLog: ITodoLogActionLog milestoneStates: MilestoneState[] }> = ({ actionLog, milestoneStates }) => { const { t } = useTranslationFrontend() if (actionLog.details.__typename !== 'IEmailType') { return null } Loading @@ -306,12 +316,17 @@ const EmailLogItemContent: FC<{ <div key={recipient.address} className={recipientRow}> <div className={recipientAddress}>{recipient.address}</div> <div className={recipientMeta}> <Tag minimal>{`Templates: ${recipient.templateCount}`}</Tag> <Tag minimal>{`${t('overview.todoList.email.templates', { count: recipient.templateCount, })}`}</Tag> {recipient.totalMilestonesCount !== undefined && ( <Tag minimal intent='primary' >{`Milestones: ${recipient.pendingMilestonesCount}/${recipient.totalMilestonesCount}`}</Tag> <Tag minimal intent='primary'>{`${t( 'overview.todoList.email.milestones', { count: recipient.pendingMilestonesCount, total: recipient.totalMilestonesCount, } )}`}</Tag> )} </div> {recipient.description && ( Loading Loading @@ -450,19 +465,22 @@ const LogItem: FC<LogItemProps> = ({ > {getTitle(actionLog)} </h5> <div className={topMetaRow}> <OverviewPillNav actionLog={actionLog} /> {questionnaireStatus && ( <QuestionnaireStatus teamStateStatus={questionnaireStatus} canBeReviewed={canBeReviewed(actionLog.details)} /> )} <EmailRecipientsTag actionLog={actionLog} /> <Timestamp formatTimestampProps={{ timestamp: actionLog.timestamp, inExerciseTime: actionLog.inExerciseTime, }} /> </div> {questionnaireStatus && ( <QuestionnaireStatus teamStateStatus={questionnaireStatus} canBeReviewed={canBeReviewed(actionLog.details)} /> )} <LogItemContent actionLog={actionLog} milestoneStates={milestoneStates ?? []} Loading
frontend/src/instructor/InstructorTodoLog/OverviewPillNav.tsx +12 −8 Original line number Diff line number Diff line import { Tag } from '@blueprintjs/core' import { css } from '@emotion/css' import { css, cx } from '@emotion/css' import type { ITodoLogActionLog } from '@inject/graphql' import { getColorScheme } from '@inject/shared' import { useNavigate } from '@tanstack/react-router' Loading @@ -8,16 +8,20 @@ import { InstructorTeamLandingPageRoute } from '../../routes/_protected/instruct export const OverviewPillNav: FC<{ actionLog: ITodoLogActionLog }> = ({ actionLog }) => { className?: string }> = ({ actionLog, className }) => { const nav = useNavigate() return ( <Tag className={css` className={cx( css` margin-right: 0.25rem; &:hover { cursor: pointer; } `} `, className )} title='Click to enter team overview' onClick={() => { nav({ Loading
locale/resources/cs/frontend.json +6 −1 Original line number Diff line number Diff line Loading @@ -514,7 +514,12 @@ "done": "Dokončeno", "notDone": "Nedokončeno", "notFoundTitle": "Žádné injecty", "notFoundDescription": "Nebyly nalezeny žádné injecty, které by mohly být zobrazeny v téhle kategorii. Prosím počkejte na nové" "notFoundDescription": "Nebyly nalezeny žádné injecty, které by mohly být zobrazeny v téhle kategorii. Prosím počkejte na nové", "email": { "templates": "Šablony: {{count}}", "milestones": "Milníky: {{count}}/{{total}}", "recipients": "Příjemci: {{count}}" } }, "instructorComments": { "title": "Komentáře od instruktorů", Loading
locale/resources/en/frontend.json +6 −1 Original line number Diff line number Diff line Loading @@ -514,7 +514,12 @@ "done": "Done", "notDone": "Not done", "notFoundTitle": "No inject items", "notFoundDescription": "There are no log items that can be shown in this category. Please await for new injects" "notFoundDescription": "There are no log items that can be shown in this category. Please await for new injects", "email": { "templates": "Templates: {{count}}", "milestones": "Milestones: {{count}}/{{total}}", "recipients": "Recipients: {{count}}" } }, "instructorComments": { "title": "Instructor comments", Loading