Commit f4a0e385 authored by Marek Veselý's avatar Marek Veselý
Browse files

Add a Room (classroom) view to analyst WITHOUT DATA

parent 8c68d656
Loading
Loading
Loading
Loading
+27 −15
Original line number Diff line number Diff line
@@ -29,11 +29,12 @@ import { useCallback, useContext, useMemo } from 'react'

import { ExercisePageRoute } from '../../routes/_layout/$exerciseId'
import { ActionLogsRoute } from '../../routes/_layout/$exerciseId/action-logs'
import { CauseAndEffectRoute } from '../../routes/_layout/$exerciseId/cuase-and-effect'
import { CauseAndEffectRoute } from '../../routes/_layout/$exerciseId/cause-and-effect'
import { EmailPageRoute } from '../../routes/_layout/$exerciseId/emails'
import { LearningObjectivesPageRoute } from '../../routes/_layout/$exerciseId/learning-objectives'
import { MilestonesPageRoute } from '../../routes/_layout/$exerciseId/milestones'
import { QuestionnairesPageRoute } from '../../routes/_layout/$exerciseId/questionnaires'
import { RoomViewRoute } from '../../routes/_layout/$exerciseId/room'
import { SelectTeamsPageRoute } from '../../routes/_layout/$exerciseId/select-teams'
import { ToolsPageRoute } from '../../routes/_layout/$exerciseId/tools'
import { ExerciseContext } from '../ExerciseContext'
@@ -98,9 +99,29 @@ export const NavigationBar: FC<NavigationBarProps> = ({
        },
      },
      icon: 'control',
      text: 'Overview',
      text: 'Performance Overview',
      end: true,
    })
    paths.push({
      link: {
        to: RoomViewRoute.to,
        params: {
          exerciseId,
        },
      },
      icon: 'grid-view',
      text: 'Room View',
    })
    paths.push({
      link: {
        to: CauseAndEffectRoute.to,
        params: {
          exerciseId,
        },
      },
      icon: 'one-to-many',
      text: 'Cause and Effect',
    })
    paths.push({
      link: {
        to: ActionLogsRoute.to,
@@ -153,7 +174,7 @@ export const NavigationBar: FC<NavigationBarProps> = ({
          },
        },
        icon: 'console',
        text: 'Tools',
        text: 'Tool Usage',
      })
    }
    if (questionnairesEnabled) {
@@ -168,16 +189,6 @@ export const NavigationBar: FC<NavigationBarProps> = ({
        text: 'Questionnaires',
      })
    }
    paths.push({
      link: {
        to: CauseAndEffectRoute.to,
        params: {
          exerciseId,
        },
      },
      icon: 'one-to-many',
      text: 'Cause and Effect',
    })
    return paths
  }, [emailsEnabled, exerciseId, questionnairesEnabled, toolsEnabled])

@@ -259,8 +270,8 @@ export const NavigationBar: FC<NavigationBarProps> = ({
      ...(exerciseId && teams.length
        ? [
            {
              id: 'pages',
              name: 'Pages',
              id: 'views',
              name: 'Views',
              node: (
                <>
                  {paths.map(path => (
@@ -306,6 +317,7 @@ export const NavigationBar: FC<NavigationBarProps> = ({
        sections={sections}
        hideNames={hide}
        showLogo
        title={exercise.name}
      />

      <div
+180 −0
Original line number Diff line number Diff line
import {
  Button,
  ButtonGroup,
  Divider,
  FormGroup,
  NumericInput,
} from '@blueprintjs/core'
import { css } from '@emotion/css'
import type { Team } from '@inject/graphql'
import type { DraggableGridConfig } from '@inject/shared'
import type { SetState } from 'ahooks/lib/createUseStorageState'
import type { FC } from 'react'

const mainContainer = css`
  display: flex;
  gap: 1rem;
`

const sizeSection = css`
  display: flex;
  gap: 1rem;
  max-width: 20rem;
`

const form = css`
  margin-bottom: 0;
`

const layoutSection = css`
  flex: 1;
  display: flex;
  justify-content: flex-end;
`

const buttonGroup = css`
  align-self: flex-start;
`

// TODO: make the config collapsible
// TODO: add export and import layout

export type RoomViewConfig = {
  hideTeamLabels: boolean
}

interface RoomViewConfigComponentProps {
  teams: Team[]
  gridConfig: DraggableGridConfig<Team>
  setGridConfig: (
    value?: SetState<DraggableGridConfig<Team>> | undefined
  ) => void
  gridConfigDefault: DraggableGridConfig<Team>
  viewConfig: RoomViewConfig
  setViewConfig: (value?: SetState<RoomViewConfig> | undefined) => void
  viewConfigDefault: RoomViewConfig
}

export const RoomViewConfigComponent: FC<RoomViewConfigComponentProps> = ({
  teams,
  gridConfig,
  setGridConfig,
  gridConfigDefault,
  viewConfig,
  setViewConfig,
  viewConfigDefault,
}) => (
  <main className={mainContainer}>
    <section className={sizeSection}>
      <FormGroup className={form} fill labelFor='columns' label='Columns'>
        <NumericInput
          id='columns'
          fill
          disabled={gridConfig.editing}
          value={gridConfig.columns}
          onValueChange={value =>
            setGridConfig(prev => ({
              ...(prev ?? gridConfigDefault),
              columns: value,
            }))
          }
          min={Math.max(1, Math.ceil(teams.length / gridConfig.rows))}
        />
      </FormGroup>
      <FormGroup className={form} fill labelFor='rows' label='Rows'>
        <NumericInput
          id='rows'
          fill
          disabled={gridConfig.editing}
          value={gridConfig.rows}
          onValueChange={value =>
            setGridConfig(prev => ({
              ...(prev ?? gridConfigDefault),
              rows: value,
            }))
          }
          min={Math.max(1, Math.ceil(teams.length / gridConfig.columns))}
        />
      </FormGroup>
    </section>
    <section className={layoutSection}>
      <ButtonGroup className={buttonGroup}>
        <Button
          icon='label'
          onClick={() =>
            setViewConfig(prev => ({
              ...(prev ?? viewConfigDefault),
              hideTeamLabels: !(prev ?? viewConfigDefault).hideTeamLabels,
            }))
          }
          text={
            <span
              style={{
                whiteSpace: 'nowrap',
              }}
            >
              {viewConfig.hideTeamLabels
                ? 'Show team labels'
                : 'Hide team labels'}
            </span>
          }
        />

        <Divider />

        {gridConfig.editing ? (
          <>
            <Button
              icon='floppy-disk'
              onClick={() =>
                setGridConfig(prev => ({
                  ...(prev ?? gridConfigDefault),
                  editing: false,
                  cells: prev?.editedCells ?? gridConfigDefault.cells,
                  editedCells: [],
                }))
              }
              intent='primary'
              text='Save layout'
            />
            <Button
              icon='cross'
              onClick={() =>
                setGridConfig(prev => ({
                  ...(prev ?? gridConfigDefault),
                  editing: false,
                  editedCells: [],
                }))
              }
              text='Cancel'
            />
          </>
        ) : (
          <>
            <Button
              icon='edit'
              onClick={() =>
                setGridConfig(prev => ({
                  ...(prev ?? gridConfigDefault),
                  editing: true,
                  editedCells: prev?.cells ?? gridConfigDefault.cells,
                }))
              }
              text='Edit layout'
            />
            <Button
              icon='reset'
              onClick={() =>
                setGridConfig(prev => ({
                  ...(prev ?? gridConfigDefault),
                  cells: gridConfigDefault.cells,
                }))
              }
              text='Reset layout'
            />
          </>
        )}
      </ButtonGroup>
    </section>
  </main>
)
+48 −21
Original line number Diff line number Diff line
@@ -17,10 +17,11 @@ import { Route as LayoutExerciseIdRouteImport } from './routes/_layout/$exercise
import { Route as LayoutExerciseIdIndexImport } from './routes/_layout/$exerciseId/index'
import { Route as LayoutExerciseIdToolsImport } from './routes/_layout/$exerciseId/tools'
import { Route as LayoutExerciseIdSelectTeamsImport } from './routes/_layout/$exerciseId/select-teams'
import { Route as LayoutExerciseIdRoomImport } from './routes/_layout/$exerciseId/room'
import { Route as LayoutExerciseIdQuestionnairesImport } from './routes/_layout/$exerciseId/questionnaires'
import { Route as LayoutExerciseIdMilestonesImport } from './routes/_layout/$exerciseId/milestones'
import { Route as LayoutExerciseIdLearningObjectivesImport } from './routes/_layout/$exerciseId/learning-objectives'
import { Route as LayoutExerciseIdCuaseAndEffectImport } from './routes/_layout/$exerciseId/cuase-and-effect'
import { Route as LayoutExerciseIdCauseAndEffectImport } from './routes/_layout/$exerciseId/cause-and-effect'
import { Route as LayoutExerciseIdActionLogsImport } from './routes/_layout/$exerciseId/action-logs'
import { Route as LayoutExerciseIdEmailsRouteImport } from './routes/_layout/$exerciseId/emails/route'
import { Route as LayoutExerciseIdEmailsIndexImport } from './routes/_layout/$exerciseId/emails/index'
@@ -65,6 +66,12 @@ const LayoutExerciseIdSelectTeamsRoute =
    getParentRoute: () => LayoutExerciseIdRouteRoute,
  } as any)

const LayoutExerciseIdRoomRoute = LayoutExerciseIdRoomImport.update({
  id: '/room',
  path: '/room',
  getParentRoute: () => LayoutExerciseIdRouteRoute,
} as any)

const LayoutExerciseIdQuestionnairesRoute =
  LayoutExerciseIdQuestionnairesImport.update({
    id: '/questionnaires',
@@ -87,10 +94,10 @@ const LayoutExerciseIdLearningObjectivesRoute =
    getParentRoute: () => LayoutExerciseIdRouteRoute,
  } as any)

const LayoutExerciseIdCuaseAndEffectRoute =
  LayoutExerciseIdCuaseAndEffectImport.update({
    id: '/cuase-and-effect',
    path: '/cuase-and-effect',
const LayoutExerciseIdCauseAndEffectRoute =
  LayoutExerciseIdCauseAndEffectImport.update({
    id: '/cause-and-effect',
    path: '/cause-and-effect',
    getParentRoute: () => LayoutExerciseIdRouteRoute,
  } as any)

@@ -170,11 +177,11 @@ declare module '@tanstack/react-router' {
      preLoaderRoute: typeof LayoutExerciseIdActionLogsImport
      parentRoute: typeof LayoutExerciseIdRouteImport
    }
    '/_layout/$exerciseId/cuase-and-effect': {
      id: '/_layout/$exerciseId/cuase-and-effect'
      path: '/cuase-and-effect'
      fullPath: '/$exerciseId/cuase-and-effect'
      preLoaderRoute: typeof LayoutExerciseIdCuaseAndEffectImport
    '/_layout/$exerciseId/cause-and-effect': {
      id: '/_layout/$exerciseId/cause-and-effect'
      path: '/cause-and-effect'
      fullPath: '/$exerciseId/cause-and-effect'
      preLoaderRoute: typeof LayoutExerciseIdCauseAndEffectImport
      parentRoute: typeof LayoutExerciseIdRouteImport
    }
    '/_layout/$exerciseId/learning-objectives': {
@@ -198,6 +205,13 @@ declare module '@tanstack/react-router' {
      preLoaderRoute: typeof LayoutExerciseIdQuestionnairesImport
      parentRoute: typeof LayoutExerciseIdRouteImport
    }
    '/_layout/$exerciseId/room': {
      id: '/_layout/$exerciseId/room'
      path: '/room'
      fullPath: '/$exerciseId/room'
      preLoaderRoute: typeof LayoutExerciseIdRoomImport
      parentRoute: typeof LayoutExerciseIdRouteImport
    }
    '/_layout/$exerciseId/select-teams': {
      id: '/_layout/$exerciseId/select-teams'
      path: '/select-teams'
@@ -264,10 +278,11 @@ const LayoutExerciseIdEmailsRouteRouteWithChildren =
interface LayoutExerciseIdRouteRouteChildren {
  LayoutExerciseIdEmailsRouteRoute: typeof LayoutExerciseIdEmailsRouteRouteWithChildren
  LayoutExerciseIdActionLogsRoute: typeof LayoutExerciseIdActionLogsRoute
  LayoutExerciseIdCuaseAndEffectRoute: typeof LayoutExerciseIdCuaseAndEffectRoute
  LayoutExerciseIdCauseAndEffectRoute: typeof LayoutExerciseIdCauseAndEffectRoute
  LayoutExerciseIdLearningObjectivesRoute: typeof LayoutExerciseIdLearningObjectivesRoute
  LayoutExerciseIdMilestonesRoute: typeof LayoutExerciseIdMilestonesRoute
  LayoutExerciseIdQuestionnairesRoute: typeof LayoutExerciseIdQuestionnairesRoute
  LayoutExerciseIdRoomRoute: typeof LayoutExerciseIdRoomRoute
  LayoutExerciseIdSelectTeamsRoute: typeof LayoutExerciseIdSelectTeamsRoute
  LayoutExerciseIdToolsRoute: typeof LayoutExerciseIdToolsRoute
  LayoutExerciseIdIndexRoute: typeof LayoutExerciseIdIndexRoute
@@ -278,11 +293,12 @@ const LayoutExerciseIdRouteRouteChildren: LayoutExerciseIdRouteRouteChildren = {
  LayoutExerciseIdEmailsRouteRoute:
    LayoutExerciseIdEmailsRouteRouteWithChildren,
  LayoutExerciseIdActionLogsRoute: LayoutExerciseIdActionLogsRoute,
  LayoutExerciseIdCuaseAndEffectRoute: LayoutExerciseIdCuaseAndEffectRoute,
  LayoutExerciseIdCauseAndEffectRoute: LayoutExerciseIdCauseAndEffectRoute,
  LayoutExerciseIdLearningObjectivesRoute:
    LayoutExerciseIdLearningObjectivesRoute,
  LayoutExerciseIdMilestonesRoute: LayoutExerciseIdMilestonesRoute,
  LayoutExerciseIdQuestionnairesRoute: LayoutExerciseIdQuestionnairesRoute,
  LayoutExerciseIdRoomRoute: LayoutExerciseIdRoomRoute,
  LayoutExerciseIdSelectTeamsRoute: LayoutExerciseIdSelectTeamsRoute,
  LayoutExerciseIdToolsRoute: LayoutExerciseIdToolsRoute,
  LayoutExerciseIdIndexRoute: LayoutExerciseIdIndexRoute,
@@ -314,10 +330,11 @@ export interface FileRoutesByFullPath {
  '/': typeof LayoutIndexRoute
  '/$exerciseId/emails': typeof LayoutExerciseIdEmailsRouteRouteWithChildren
  '/$exerciseId/action-logs': typeof LayoutExerciseIdActionLogsRoute
  '/$exerciseId/cuase-and-effect': typeof LayoutExerciseIdCuaseAndEffectRoute
  '/$exerciseId/cause-and-effect': typeof LayoutExerciseIdCauseAndEffectRoute
  '/$exerciseId/learning-objectives': typeof LayoutExerciseIdLearningObjectivesRoute
  '/$exerciseId/milestones': typeof LayoutExerciseIdMilestonesRoute
  '/$exerciseId/questionnaires': typeof LayoutExerciseIdQuestionnairesRoute
  '/$exerciseId/room': typeof LayoutExerciseIdRoomRoute
  '/$exerciseId/select-teams': typeof LayoutExerciseIdSelectTeamsRoute
  '/$exerciseId/tools': typeof LayoutExerciseIdToolsRoute
  '/$exerciseId/': typeof LayoutExerciseIdIndexRoute
@@ -329,10 +346,11 @@ export interface FileRoutesByFullPath {
export interface FileRoutesByTo {
  '/': typeof LayoutIndexRoute
  '/$exerciseId/action-logs': typeof LayoutExerciseIdActionLogsRoute
  '/$exerciseId/cuase-and-effect': typeof LayoutExerciseIdCuaseAndEffectRoute
  '/$exerciseId/cause-and-effect': typeof LayoutExerciseIdCauseAndEffectRoute
  '/$exerciseId/learning-objectives': typeof LayoutExerciseIdLearningObjectivesRoute
  '/$exerciseId/milestones': typeof LayoutExerciseIdMilestonesRoute
  '/$exerciseId/questionnaires': typeof LayoutExerciseIdQuestionnairesRoute
  '/$exerciseId/room': typeof LayoutExerciseIdRoomRoute
  '/$exerciseId/select-teams': typeof LayoutExerciseIdSelectTeamsRoute
  '/$exerciseId/tools': typeof LayoutExerciseIdToolsRoute
  '/$exerciseId': typeof LayoutExerciseIdIndexRoute
@@ -348,10 +366,11 @@ export interface FileRoutesById {
  '/_layout/': typeof LayoutIndexRoute
  '/_layout/$exerciseId/emails': typeof LayoutExerciseIdEmailsRouteRouteWithChildren
  '/_layout/$exerciseId/action-logs': typeof LayoutExerciseIdActionLogsRoute
  '/_layout/$exerciseId/cuase-and-effect': typeof LayoutExerciseIdCuaseAndEffectRoute
  '/_layout/$exerciseId/cause-and-effect': typeof LayoutExerciseIdCauseAndEffectRoute
  '/_layout/$exerciseId/learning-objectives': typeof LayoutExerciseIdLearningObjectivesRoute
  '/_layout/$exerciseId/milestones': typeof LayoutExerciseIdMilestonesRoute
  '/_layout/$exerciseId/questionnaires': typeof LayoutExerciseIdQuestionnairesRoute
  '/_layout/$exerciseId/room': typeof LayoutExerciseIdRoomRoute
  '/_layout/$exerciseId/select-teams': typeof LayoutExerciseIdSelectTeamsRoute
  '/_layout/$exerciseId/tools': typeof LayoutExerciseIdToolsRoute
  '/_layout/$exerciseId/': typeof LayoutExerciseIdIndexRoute
@@ -368,10 +387,11 @@ export interface FileRouteTypes {
    | '/'
    | '/$exerciseId/emails'
    | '/$exerciseId/action-logs'
    | '/$exerciseId/cuase-and-effect'
    | '/$exerciseId/cause-and-effect'
    | '/$exerciseId/learning-objectives'
    | '/$exerciseId/milestones'
    | '/$exerciseId/questionnaires'
    | '/$exerciseId/room'
    | '/$exerciseId/select-teams'
    | '/$exerciseId/tools'
    | '/$exerciseId/'
@@ -382,10 +402,11 @@ export interface FileRouteTypes {
  to:
    | '/'
    | '/$exerciseId/action-logs'
    | '/$exerciseId/cuase-and-effect'
    | '/$exerciseId/cause-and-effect'
    | '/$exerciseId/learning-objectives'
    | '/$exerciseId/milestones'
    | '/$exerciseId/questionnaires'
    | '/$exerciseId/room'
    | '/$exerciseId/select-teams'
    | '/$exerciseId/tools'
    | '/$exerciseId'
@@ -399,10 +420,11 @@ export interface FileRouteTypes {
    | '/_layout/'
    | '/_layout/$exerciseId/emails'
    | '/_layout/$exerciseId/action-logs'
    | '/_layout/$exerciseId/cuase-and-effect'
    | '/_layout/$exerciseId/cause-and-effect'
    | '/_layout/$exerciseId/learning-objectives'
    | '/_layout/$exerciseId/milestones'
    | '/_layout/$exerciseId/questionnaires'
    | '/_layout/$exerciseId/room'
    | '/_layout/$exerciseId/select-teams'
    | '/_layout/$exerciseId/tools'
    | '/_layout/$exerciseId/'
@@ -446,10 +468,11 @@ export const routeTree = rootRoute
      "children": [
        "/_layout/$exerciseId/emails",
        "/_layout/$exerciseId/action-logs",
        "/_layout/$exerciseId/cuase-and-effect",
        "/_layout/$exerciseId/cause-and-effect",
        "/_layout/$exerciseId/learning-objectives",
        "/_layout/$exerciseId/milestones",
        "/_layout/$exerciseId/questionnaires",
        "/_layout/$exerciseId/room",
        "/_layout/$exerciseId/select-teams",
        "/_layout/$exerciseId/tools",
        "/_layout/$exerciseId/",
@@ -472,8 +495,8 @@ export const routeTree = rootRoute
      "filePath": "_layout/$exerciseId/action-logs.tsx",
      "parent": "/_layout/$exerciseId"
    },
    "/_layout/$exerciseId/cuase-and-effect": {
      "filePath": "_layout/$exerciseId/cuase-and-effect.tsx",
    "/_layout/$exerciseId/cause-and-effect": {
      "filePath": "_layout/$exerciseId/cause-and-effect.tsx",
      "parent": "/_layout/$exerciseId"
    },
    "/_layout/$exerciseId/learning-objectives": {
@@ -488,6 +511,10 @@ export const routeTree = rootRoute
      "filePath": "_layout/$exerciseId/questionnaires.tsx",
      "parent": "/_layout/$exerciseId"
    },
    "/_layout/$exerciseId/room": {
      "filePath": "_layout/$exerciseId/room.tsx",
      "parent": "/_layout/$exerciseId"
    },
    "/_layout/$exerciseId/select-teams": {
      "filePath": "_layout/$exerciseId/select-teams.tsx",
      "parent": "/_layout/$exerciseId"
+4 −5
Original line number Diff line number Diff line
@@ -2,15 +2,14 @@ import { css } from '@emotion/css'
import { useSubscribedTeams } from '@inject/frontend'
import type { Team } from '@inject/graphql'
import { createFileRoute } from '@tanstack/react-router'
import { useContext } from 'react'
import { ExerciseContext, TeamSelectPage } from '../../../components'
import { TeamSelectPage } from '../../../components'
import { CauseAndEffectTree } from '../../../components/CauseAndEffectTree'

const RouteComponent = () => {
  const { exercise } = useContext(ExerciseContext)
  const { exerciseId } = CauseAndEffectRoute.useParams()

  const teams = useSubscribedTeams({
    exerciseId: exercise.id,
    exerciseId,
    context: 'analyst',
  })

@@ -33,7 +32,7 @@ const RouteComponent = () => {
  )
}

export const Route = createFileRoute('/_layout/$exerciseId/cuase-and-effect')({
export const Route = createFileRoute('/_layout/$exerciseId/cause-and-effect')({
  component: RouteComponent,
})

+2 −14
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@ import {
  TeamsDisplay,
  useSubscribedTeams,
} from '@inject/frontend'
import { useResize } from '@inject/shared'
import { Keys, useResize } from '@inject/shared'
import { createFileRoute } from '@tanstack/react-router'
import { useContext } from 'react'

@@ -35,8 +35,6 @@ const RouteComponent = () => {

  const { exercise } = useContext(ExerciseContext)

  const { name } = exercise

  const teams = useSubscribedTeams({ exerciseId, context: 'analyst' })

  const {
@@ -45,7 +43,7 @@ const RouteComponent = () => {
    handleMouseMove,
    handleMouseUp,
    leftWidth,
  } = useResize()
  } = useResize(Keys.getAnalystOverviewLeftWidthKey(exerciseId))

  return (
    <main
@@ -65,16 +63,6 @@ const RouteComponent = () => {
          min-width: 35rem;
        `}
      >
        <h1
          className={css`
            margin: 0;
          `}
        >
          {name}
        </h1>

        <Divider />

        <LearningObjectives teamIds={teams.map(team => team.id)} showTeams />

        <Divider />
Loading