Commit dba8d293 authored by Adam Parák's avatar Adam Parák 💬 Committed by Marek Veselý
Browse files

feat: rework milestones to have selectors

parent 52fed210
Loading
Loading
Loading
Loading
+25 −50
Original line number Diff line number Diff line
@@ -36,41 +36,22 @@ const AllMilestoneIndicator: FC<MilestoneIndicatorProps> = ({
  const filteredStates = states.filter(
    state => milestone.id === state.milestone.id
  )
  const subbedTeams = filteredStates.filter(state =>
  const subbedTeams =
    teamIds.length !== 0
      ? filteredStates.filter(state =>
          state.teamIds.some(id => teamIds.includes(id))
        )
  const reached = filteredStates.every(({ reached }) => reached)
  const unreachedAll = filteredStates.every(({ reached }) => !reached)
  const areEqualSize = filteredStates.length === subbedTeams.length
  const contentTag = areEqualSize
    ? `Partially reached ${subbedTeams.filter(state => state.reached).length}/${subbedTeams.length}`
    : `Partially reached ${subbedTeams.filter(state => state.reached).length}/${subbedTeams.length} (for all ${filteredStates.filter(state => state.reached).length}/${filteredStates.length})`
  const reachedTag = areEqualSize
    ? 'Reached'
    : `Reached for given selection (for all ${filteredStates.filter(state => state.reached).length}/${filteredStates.length})`
      : filteredStates
  const reached = subbedTeams.every(({ reached }) => reached)
  const unreachedAll = subbedTeams.every(({ reached }) => !reached)
  const contentTag = `Partially reached ${subbedTeams.filter(state => state.reached).length}/${subbedTeams.length}`
  /*
   * the switch will change its state even if the alert is cancelled;
   * we track of the state separately to revert it if the alert is cancelled
   */
  const [open, setOpen] = useState(false)
  const confirmAll = async () => {
    filteredStates
      .filter(state => state.reached === reached)
      .forEach(state => {
        state.teamIds.forEach(teamId => {
          client
            .mutation<unknown, VariablesOf<typeof SetMilestone>>(SetMilestone, {
              milestone: milestone.name,
              activate: !reached,
              teamId,
            })
            .toPromise()
        })
      })
    setOpen(false)
  }
  const confirmSome = async () => {
    filteredStates
  const confirm = async () => {
    subbedTeams
      .filter(state => state.reached === reached)
      .forEach(state => {
        state.teamIds.forEach(teamId => {
@@ -107,7 +88,7 @@ const AllMilestoneIndicator: FC<MilestoneIndicatorProps> = ({
                reached ? (
                  <StyledTag
                    isAchieved
                    content={reachedTag}
                    content='Reached'
                    className={timestamp}
                  />
                ) : unreachedAll ? (
@@ -118,7 +99,7 @@ const AllMilestoneIndicator: FC<MilestoneIndicatorProps> = ({
                  />
                ) : (
                  <StyledTag
                    isAchieved={false}
                    isRead={false}
                    content={contentTag}
                    className={timestamp}
                  />
@@ -149,10 +130,11 @@ const AllMilestoneIndicator: FC<MilestoneIndicatorProps> = ({
        className={Classes.ALERT}
      >
        <div className={Classes.ALERT_BODY}>
          <Icon icon='warning-sign' size={40} intent='warning' />
          <Icon icon='warning-sign' size={40} intent={teamIds.length === 0 ? 'danger' : 'warning'} />
          <div className={Classes.ALERT_CONTENTS}>
            Are you sure you want to mark this milestone as{' '}
            {reached ? 'not reached' : 'reached'} for selected or all teams?
            {reached ? 'not reached' : 'reached'} for
            {teamIds.length === 0 ? ' ALL teams' : ' selected teams'}
          </div>
        </div>
        <div
@@ -161,26 +143,19 @@ const AllMilestoneIndicator: FC<MilestoneIndicatorProps> = ({
            [css`
              flex-wrap: wrap;
              gap: 0.5rem;
            `]: !areEqualSize,
            `]: true,
          })}
        >
          {!areEqualSize && (
          <Button
              intent='primary'
              title='Sets milestone for instructed teams'
              onClick={confirmSome}
              fill={!areEqualSize}
            >
              Confirm (for {teamIds.length})
            </Button>
          )}
          <Button
            intent='danger'
            title='Sets milestone for all teams present in the exercise'
            onClick={confirmAll}
            fill={!areEqualSize}
            intent={teamIds.length === 0 ? 'danger' : 'warning'}
            title={
              teamIds.length === 0
                ? 'Sets milestone for all teams'
                : 'Sets milestone for selected teams'
            }
            onClick={confirm}
          >
            Confirm{!areEqualSize && ' (for all)'}
            Confirm
          </Button>
          <Button onClick={() => setOpen(false)}>Cancel</Button>
        </div>
+6 −8
Original line number Diff line number Diff line
@@ -6,17 +6,16 @@ import {
  GetMilestones,
} from '@inject/graphql/queries'
import { type FC } from 'react'
import useGetSelectedTeams from '../InstructorTeamSelector/useGetSelectedTeams'
import AllMilestoneIndicator from './AllMilestoneIndicator'
import MilestoneIndicator from './MilestoneIndicator'

interface InstructorMilestonesProps {
  exerciseId: string
  teamId?: string
  teamIds: string[]
}

const InstructorMilestones: FC<InstructorMilestonesProps> = ({
  teamId,
  teamIds,
  exerciseId,
}) => {
  const [{ data: states }] = useTypedQuery({
@@ -27,7 +26,6 @@ const InstructorMilestones: FC<InstructorMilestonesProps> = ({
    query: GetMilestones,
    variables: { exerciseId },
  })
  const teamIds = useGetSelectedTeams(exerciseId).map(team => team.id)

  return (
    <div
@@ -41,17 +39,17 @@ const InstructorMilestones: FC<InstructorMilestonesProps> = ({
    >
      <h3 style={{ order: 0, marginLeft: '0.5rem' }}>Reached</h3>
      <h3 style={{ order: 2, marginLeft: '0.5rem' }}>Not reached</h3>
      {teamId &&
      {teamIds.length === 1 &&
        states?.analyticsMilestones
          .filter(milestone => milestone.teamIds.includes(teamId))
          .filter(milestone => milestone.teamIds.includes(teamIds[0]))
          .map(milestone => (
            <MilestoneIndicator
              milestone={milestone}
              key={milestone.milestone.id}
              teamId={teamId}
              teamId={teamIds[0]}
            />
          ))}
      {!teamId &&
      {teamIds.length !== 1 &&
        milestones?.milestones.map(milestone => (
          <AllMilestoneIndicator
            teamIds={teamIds}
+54 −4
Original line number Diff line number Diff line
@@ -4,9 +4,10 @@ import {
} from '@/clientsettings/vars/milestones'
import Sidebar from '@/components/Sidebar'
import InstructorMilestones from '@/instructor/InstructorMilestones'
import { Button } from '@blueprintjs/core'
import useGetSelectedTeams from '@/instructor/InstructorTeamSelector/useGetSelectedTeams'
import { Button, ButtonGroup } from '@blueprintjs/core'
import { css } from '@emotion/css'
import type { FC } from 'react'
import { useLayoutEffect, useState, type FC } from 'react'
import { createPortal } from 'react-dom'

const MilestonesButton: FC<{
@@ -18,6 +19,28 @@ const MilestonesButton: FC<{
  const open = useMilestonesOpen()

  const portalRef = document.getElementById('rightPanel')
  const teamIds = useGetSelectedTeams(exerciseId).map(team => team.id)

  const [select, setSelect] = useState<null | 'team' | 'selection' | 'all'>(
    null
  )
  const defaultSelect: typeof select = !select
    ? teamId
      ? 'team'
      : 'selection'
    : select
  const selectTeamIds =
    defaultSelect === 'all'
      ? []
      : defaultSelect === 'team' && teamId
        ? [teamId]
        : teamIds

  useLayoutEffect(() => {
    if (select !== null) {
      setSelect(null)
    }
  }, [teamId])

  return (
    <>
@@ -45,14 +68,41 @@ const MilestonesButton: FC<{
            className={css`
              width: 22rem;
            `}
            title={teamId ? 'Milestones for team' : 'Milestones for all'}
            title='Milestones'
            sections={[
              {
                id: 'selectors',
                node: (
                  <ButtonGroup minimal fill>
                    <Button
                      disabled={defaultSelect === 'all'}
                      onClick={() => setSelect('all')}
                    >
                      All
                    </Button>
                    {teamId && (
                      <Button
                        disabled={defaultSelect === 'team'}
                        onClick={() => setSelect('team')}
                      >
                        Team
                      </Button>
                    )}
                    <Button
                      disabled={defaultSelect === 'selection'}
                      onClick={() => setSelect('selection')}
                    >
                      Selected
                    </Button>
                  </ButtonGroup>
                ),
              },
              {
                id: 'reached',
                node: (
                  <InstructorMilestones
                    exerciseId={exerciseId}
                    teamId={teamId}
                    teamIds={selectTeamIds}
                  />
                ),
              },