Verified Commit ef9ec1e1 authored by Marek Veselý's avatar Marek Veselý
Browse files

feat: make exercise and definition lists sortable

parent 9031cc75
Loading
Loading
Loading
Loading
+17 −5
Original line number Diff line number Diff line
@@ -4,7 +4,14 @@ import { css, cx } from '@emotion/css'
import type { Definition } from '@inject/graphql'
import { GetDefinitions, useTypedQuery } from '@inject/graphql'
import type { Column, Row } from '@inject/shared'
import { notEmpty, Table, timedFormatter } from '@inject/shared'
import {
  notEmpty,
  numberSortingFunction,
  stringSortingFunction,
  Table,
  timedFormatter,
  timestampSortingFunction,
} from '@inject/shared'
import type { FC } from 'react'
import { useMemo } from 'react'
import DefinitionButtons from './DefinitionButtons'
@@ -41,33 +48,38 @@ const DefinitionList: FC<DefinitionListProps> = ({ className }) => {
        style: { textAlign: 'left' },
        renderValue: definition => definition.name,
        className: verticallyCentered,
        sortingFunction: (a, b) => stringSortingFunction(a.name, b.name),
      },
      {
        id: 'version',
        name: 'Version',
        style: { textAlign: 'right', width: '10ch' },
        style: { textAlign: 'right', width: '12ch' },
        renderValue: definition => definition.version,
        className: verticallyCentered,
        sortingFunction: (a, b) => stringSortingFunction(a.version, b.version),
      },
      {
        id: 'roles',
        name: 'Roles',
        style: { textAlign: 'right', width: '10ch' },
        style: { textAlign: 'right', width: '12ch' },
        renderValue: definition =>
          definition.roles.length || <Cross color={Colors.RED3} />,

        className: cx(verticallyCentered, hideOnSmallScreen),
        sortingFunction: (a, b) =>
          numberSortingFunction(a.roles.length, b.roles.length),
      },
      {
        id: 'uploaded-at',
        name: 'Uploaded',
        style: { textAlign: 'right', width: '12ch' },
        style: { textAlign: 'right', width: '14ch' },
        renderValue: definition =>
          timedFormatter({
            datetime: new Date(definition.timestampCreated),
            minimal: true,
          }),
        className: verticallyCentered,
        sortingFunction: (a, b) =>
          timestampSortingFunction(a.timestampCreated, b.timestampCreated),
      },
      {
        id: 'actions',
+32 −2
Original line number Diff line number Diff line
@@ -4,7 +4,12 @@ import { css, cx } from '@emotion/css'
import type { Exercise } from '@inject/graphql'
import { useExercisesSubscription } from '@inject/graphql'
import type { Column, Row } from '@inject/shared'
import { Table } from '@inject/shared'
import {
  exerciseNameSortingFunction,
  numberSortingFunction,
  stringSortingFunction,
  Table,
} from '@inject/shared'
import type { NavigateOptions } from '@tanstack/react-router'
import type { FC } from 'react'
import { useMemo } from 'react'
@@ -12,6 +17,25 @@ import { synchronousExerciseState } from '../../utils'
import { ExerciseTags } from '../Tags/ExerciseTags'
import { ExerciseButtons } from './ExerciseButtons'

const exerciseTagsSortingFunction = (a: Exercise, b: Exercise) => {
  // sort alphabetically
  const statusOrder: Record<string, number> = {
    FINISHED: 0,
    NOT_STARTED: 1,
    ON_DEMAND: 2,
    RUNNING: 3,
    STOPPED: 4,
  }

  const getStatusRank = (exercise: Exercise) => {
    if (exercise.onDemand) return statusOrder.ON_DEMAND
    const status = synchronousExerciseState(exercise).status
    return statusOrder[status] ?? Number.MAX_SAFE_INTEGER
  }

  return getStatusRank(a) - getStatusRank(b)
}

const verticallyCentered = css`
  vertical-align: middle;
`
@@ -76,6 +100,7 @@ export const ExerciseList: FC<
        style: { textAlign: 'left', width: '100%' },
        renderValue: exercise => exercise.name,
        className: verticallyCentered,
        sortingFunction: (a, b) => exerciseNameSortingFunction(a.name, b.name),
      },
      {
        id: 'definition',
@@ -84,13 +109,17 @@ export const ExerciseList: FC<
        renderValue: exercise =>
          exercise.definition?.name || exercise.definition?.id,
        className: verticallyCentered,
        sortingFunction: (a, b) =>
          stringSortingFunction(a.definition.name, b.definition.name),
      },
      {
        id: 'teams',
        name: 'Teams',
        style: { textAlign: 'right', width: '8ch' },
        style: { textAlign: 'right', width: '10ch' },
        renderValue: exercise => exercise.teams.length,
        className: cx(verticallyCentered, hideOnSmallScreen),
        sortingFunction: (a, b) =>
          numberSortingFunction(a.teams.length, b.teams.length),
      },
      {
        id: 'status',
@@ -103,6 +132,7 @@ export const ExerciseList: FC<
          />
        ),
        className: verticallyCentered,
        sortingFunction: exerciseTagsSortingFunction,
      },
      {
        id: 'actions',
+11 −3
Original line number Diff line number Diff line
@@ -8,10 +8,12 @@ export const stringSortingFunction = (a: string, b: string) =>
 * The purpose of this function is to sort team names in a way that
 * "team-2" comes before "team-10", which is not the case with the
 * default string sorting function.
 *
 * Applies to teams, exercises, ...
 */
export const teamNameSortingFunction = (a: string, b: string) => {
  // If custom team name is used, sort as a regular string
  if (!a.startsWith('team-') || !b.startsWith('team-')) {
export const prefixSortingFunction = (a: string, b: string, prefix: string) => {
  // If custom name is used, sort as a regular string
  if (!a.startsWith(prefix) || !b.startsWith(prefix)) {
    return stringSortingFunction(a, b)
  }
  const aNumber = parseInt(a.slice(5), 10)
@@ -22,6 +24,12 @@ export const teamNameSortingFunction = (a: string, b: string) => {
  return aNumber - bNumber
}

export const teamNameSortingFunction = (a: string, b: string) =>
  prefixSortingFunction(a, b, 'team-')

export const exerciseNameSortingFunction = (a: string, b: string) =>
  prefixSortingFunction(a, b, 'Exercise ')

export const booleanSortingFunction = (a: boolean, b: boolean) =>
  Number(b) - Number(a)