瀏覽代碼

getting training form to work

Tomi Cvetic 5 年之前
父節點
當前提交
59d617f421

+ 2 - 2
codegen.yml

@@ -1,12 +1,12 @@
 overwrite: true
-schema: 
+schema:
   - backend/schema.graphql
 
 documents: 'frontend/src/**/*.graphql'
 generates:
   frontend/src/gql/index.tsx:
     config:
-      skipTypename: false
+      skipTypename: true
       maybeValue: T | undefined
       withHooks: true
       withComponent: false

+ 35 - 179
frontend/src/gql/index.tsx

@@ -15,7 +15,6 @@ export type Scalars = {
 };
 
 export type Block = Node & {
-   __typename?: 'Block',
   id: Scalars['ID'],
   title: Scalars['String'],
   description?: Maybe<Scalars['String']>,
@@ -89,7 +88,6 @@ export type BlockCreateWithoutBlocksInput = {
 };
 
 export type BlockInstance = Node & {
-   __typename?: 'BlockInstance',
   id: Scalars['ID'],
   block: Block,
   order: Scalars['Int'],
@@ -379,7 +377,6 @@ export type BlockWhereUniqueInput = {
 };
 
 export type Comment = Node & {
-   __typename?: 'Comment',
   id: Scalars['ID'],
   text: Scalars['String'],
   author: User,
@@ -604,7 +601,6 @@ export type CommentWhereUniqueInput = {
 
 
 export type Exercise = Node & {
-   __typename?: 'Exercise',
   id: Scalars['ID'],
   name: Scalars['String'],
   description: Scalars['String'],
@@ -646,7 +642,6 @@ export type ExerciseCreatevideosInput = {
 };
 
 export type ExerciseInstance = Node & {
-   __typename?: 'ExerciseInstance',
   id: Scalars['ID'],
   exercise: Exercise,
   order: Scalars['Int'],
@@ -880,7 +875,6 @@ export type ExerciseWhereUniqueInput = {
 };
 
 export type Format = Node & {
-   __typename?: 'Format',
   id: Scalars['ID'],
   name: Scalars['String'],
   description: Scalars['String'],
@@ -1001,7 +995,6 @@ export type FormatWhereUniqueInput = {
 };
 
 export type Mutation = {
-   __typename?: 'Mutation',
   createUser: User,
   updateUser?: Maybe<User>,
   deleteUser?: Maybe<User>,
@@ -1105,7 +1098,6 @@ export enum Permission {
 }
 
 export type Query = {
-   __typename?: 'Query',
   currentUser: User,
   user?: Maybe<User>,
   users: Array<User>,
@@ -1218,7 +1210,6 @@ export type QueryExercisesArgs = {
 };
 
 export type Rating = Node & {
-   __typename?: 'Rating',
   id: Scalars['ID'],
   user: User,
   value: Scalars['Int'],
@@ -1478,7 +1469,6 @@ export type RatingWhereUniqueInput = {
 };
 
 export type Track = Node & {
-   __typename?: 'Track',
   id: Scalars['ID'],
   title: Scalars['String'],
   artist: Scalars['String'],
@@ -1649,7 +1639,6 @@ export type TrackWhereUniqueInput = {
 };
 
 export type Training = Node & {
-   __typename?: 'Training',
   id: Scalars['ID'],
   title: Scalars['String'],
   type: TrainingType,
@@ -1714,7 +1703,6 @@ export enum TrainingOrderByInput {
 }
 
 export type TrainingType = Node & {
-   __typename?: 'TrainingType',
   id: Scalars['ID'],
   name: Scalars['String'],
   description: Scalars['String'],
@@ -1984,7 +1972,6 @@ export type TrainingWhereInput = {
 };
 
 export type User = Node & {
-   __typename?: 'User',
   id: Scalars['ID'],
   email: Scalars['String'],
   name: Scalars['String'],
@@ -2264,64 +2251,31 @@ export type UserWhereUniqueInput = {
 };
 
 export type ExerciseContentFragment = (
-  { __typename?: 'ExerciseInstance' }
-  & Pick<ExerciseInstance, 'id' | 'order' | 'repetitions' | 'variation'>
-  & { exercise: (
-    { __typename?: 'Exercise' }
-    & Pick<Exercise, 'id' | 'name' | 'description' | 'videos' | 'pictures' | 'targets' | 'baseExercise'>
-  ) }
+  Pick<ExerciseInstance, 'id' | 'order' | 'repetitions' | 'variation'>
+  & { exercise: Pick<Exercise, 'id' | 'name' | 'description' | 'videos' | 'pictures' | 'targets' | 'baseExercise'> }
 );
 
 export type BlockContentFragment = (
-  { __typename?: 'Block' }
-  & Pick<Block, 'id' | 'title' | 'description' | 'videos' | 'pictures' | 'duration' | 'rest'>
-  & { format: (
-    { __typename?: 'Format' }
-    & Pick<Format, 'id' | 'name' | 'description'>
-  ), blocks: Maybe<Array<(
-    { __typename?: 'BlockInstance' }
-    & Pick<BlockInstance, 'id' | 'order' | 'rounds' | 'variation'>
-    & { block: (
-      { __typename?: 'Block' }
-      & BlockHintFragment
-    ) }
-  )>>, exercises: Maybe<Array<(
-    { __typename?: 'ExerciseInstance' }
-    & ExerciseContentFragment
-  )>> }
+  Pick<Block, 'id' | 'title' | 'description' | 'videos' | 'pictures' | 'duration' | 'rest'>
+  & { format: Pick<Format, 'id' | 'name' | 'description'>, blocks: Maybe<Array<(
+    Pick<BlockInstance, 'id' | 'order' | 'rounds' | 'variation'>
+    & { block: BlockHintFragment }
+  )>>, exercises: Maybe<Array<ExerciseContentFragment>> }
 );
 
 export type BlockHintFragment = (
-  { __typename?: 'Block' }
-  & Pick<Block, 'id' | 'title' | 'description' | 'videos' | 'pictures' | 'duration' | 'rest'>
-  & { format: (
-    { __typename?: 'Format' }
-    & Pick<Format, 'id' | 'name' | 'description'>
-  ), blocks: Maybe<Array<(
-    { __typename?: 'BlockInstance' }
-    & Pick<BlockInstance, 'id'>
-  )>>, exercises: Maybe<Array<(
-    { __typename?: 'ExerciseInstance' }
-    & ExerciseContentFragment
-  )>> }
+  Pick<Block, 'id' | 'title' | 'description' | 'videos' | 'pictures' | 'duration' | 'rest'>
+  & { format: Pick<Format, 'id' | 'name' | 'description'>, blocks: Maybe<Array<Pick<BlockInstance, 'id'>>>, exercises: Maybe<Array<ExerciseContentFragment>> }
 );
 
 export type SubBlockFragment = (
-  { __typename?: 'BlockInstance' }
-  & Pick<BlockInstance, 'id' | 'order' | 'rounds' | 'variation'>
-  & { block: (
-    { __typename?: 'Block' }
-    & BlockContentFragment
-  ) }
+  Pick<BlockInstance, 'id' | 'order' | 'rounds' | 'variation'>
+  & { block: BlockContentFragment }
 );
 
 export type SubBlockHintFragment = (
-  { __typename?: 'BlockInstance' }
-  & Pick<BlockInstance, 'id' | 'order' | 'rounds' | 'variation'>
-  & { block: (
-    { __typename?: 'Block' }
-    & BlockHintFragment
-  ) }
+  Pick<BlockInstance, 'id' | 'order' | 'rounds' | 'variation'>
+  & { block: BlockHintFragment }
 );
 
 export type TrainingQueryVariables = {
@@ -2329,60 +2283,28 @@ export type TrainingQueryVariables = {
 };
 
 
-export type TrainingQuery = (
-  { __typename?: 'Query' }
-  & { training: Maybe<(
-    { __typename?: 'Training' }
-    & Pick<Training, 'id' | 'title' | 'createdAt' | 'trainingDate' | 'location' | 'attendance' | 'published'>
-    & { type: (
-      { __typename?: 'TrainingType' }
-      & Pick<TrainingType, 'id' | 'name' | 'description'>
-    ), blocks: Maybe<Array<(
-      { __typename?: 'BlockInstance' }
-      & SubBlockFragment
-    )>> }
-  )> }
-);
+export type TrainingQuery = { training: Maybe<(
+    Pick<Training, 'id' | 'title' | 'createdAt' | 'trainingDate' | 'location' | 'attendance' | 'published'>
+    & { type: Pick<TrainingType, 'id' | 'name' | 'description'>, blocks: Maybe<Array<SubBlockFragment>> }
+  )> };
 
 export type TrainingsQueryVariables = {};
 
 
-export type TrainingsQuery = (
-  { __typename?: 'Query' }
-  & { trainings: Array<(
-    { __typename?: 'Training' }
-    & Pick<Training, 'id' | 'title' | 'trainingDate' | 'location' | 'published'>
-    & { type: (
-      { __typename?: 'TrainingType' }
-      & Pick<TrainingType, 'id' | 'name' | 'description'>
-    ), blocks: Maybe<Array<(
-      { __typename?: 'BlockInstance' }
-      & SubBlockHintFragment
-    )>> }
-  )> }
-);
+export type TrainingsQuery = { trainings: Array<(
+    Pick<Training, 'id' | 'title' | 'trainingDate' | 'location' | 'published'>
+    & { type: Pick<TrainingType, 'id' | 'name' | 'description'>, blocks: Maybe<Array<SubBlockHintFragment>> }
+  )> };
 
 export type TrainingTypesQueryVariables = {};
 
 
-export type TrainingTypesQuery = (
-  { __typename?: 'Query' }
-  & { trainingTypes: Array<(
-    { __typename?: 'TrainingType' }
-    & Pick<TrainingType, 'id' | 'name' | 'description'>
-  )> }
-);
+export type TrainingTypesQuery = { trainingTypes: Array<Pick<TrainingType, 'id' | 'name' | 'description'>> };
 
 export type FormatsQueryVariables = {};
 
 
-export type FormatsQuery = (
-  { __typename?: 'Query' }
-  & { formats: Array<(
-    { __typename?: 'Format' }
-    & Pick<Format, 'id' | 'name' | 'description'>
-  )> }
-);
+export type FormatsQuery = { formats: Array<Pick<Format, 'id' | 'name' | 'description'>> };
 
 export type CreateTrainingMutationVariables = {
   title: Scalars['String'],
@@ -2395,13 +2317,7 @@ export type CreateTrainingMutationVariables = {
 };
 
 
-export type CreateTrainingMutation = (
-  { __typename?: 'Mutation' }
-  & { createTraining: (
-    { __typename?: 'Training' }
-    & Pick<Training, 'id'>
-  ) }
-);
+export type CreateTrainingMutation = { createTraining: Pick<Training, 'id'> };
 
 export type CreateTrainingTypeMutationVariables = {
   name: Scalars['String'],
@@ -2409,13 +2325,7 @@ export type CreateTrainingTypeMutationVariables = {
 };
 
 
-export type CreateTrainingTypeMutation = (
-  { __typename?: 'Mutation' }
-  & { createTrainingType: (
-    { __typename?: 'TrainingType' }
-    & Pick<TrainingType, 'id'>
-  ) }
-);
+export type CreateTrainingTypeMutation = { createTrainingType: Pick<TrainingType, 'id'> };
 
 export type CreateFormatMutationVariables = {
   name: Scalars['String'],
@@ -2423,24 +2333,12 @@ export type CreateFormatMutationVariables = {
 };
 
 
-export type CreateFormatMutation = (
-  { __typename?: 'Mutation' }
-  & { createFormat: (
-    { __typename?: 'Format' }
-    & Pick<Format, 'id'>
-  ) }
-);
+export type CreateFormatMutation = { createFormat: Pick<Format, 'id'> };
 
 export type UsersQueryVariables = {};
 
 
-export type UsersQuery = (
-  { __typename?: 'Query' }
-  & { users: Array<(
-    { __typename?: 'User' }
-    & Pick<User, 'id' | 'email' | 'name' | 'permissions' | 'interests'>
-  )> }
-);
+export type UsersQuery = { users: Array<Pick<User, 'id' | 'email' | 'name' | 'permissions' | 'interests'>> };
 
 export type UserSignupMutationVariables = {
   email: Scalars['String'],
@@ -2449,13 +2347,7 @@ export type UserSignupMutationVariables = {
 };
 
 
-export type UserSignupMutation = (
-  { __typename?: 'Mutation' }
-  & { userSignup: (
-    { __typename?: 'User' }
-    & Pick<User, 'id' | 'email' | 'name'>
-  ) }
-);
+export type UserSignupMutation = { userSignup: Pick<User, 'id' | 'email' | 'name'> };
 
 export type UserLoginMutationVariables = {
   email: Scalars['String'],
@@ -2463,42 +2355,24 @@ export type UserLoginMutationVariables = {
 };
 
 
-export type UserLoginMutation = (
-  { __typename?: 'Mutation' }
-  & { userLogin: (
-    { __typename?: 'User' }
-    & Pick<User, 'id' | 'email' | 'name'>
-  ) }
-);
+export type UserLoginMutation = { userLogin: Pick<User, 'id' | 'email' | 'name'> };
 
 export type UserLogoutMutationVariables = {};
 
 
-export type UserLogoutMutation = (
-  { __typename?: 'Mutation' }
-  & Pick<Mutation, 'userLogout'>
-);
+export type UserLogoutMutation = Pick<Mutation, 'userLogout'>;
 
 export type CurrentUserQueryVariables = {};
 
 
-export type CurrentUserQuery = (
-  { __typename?: 'Query' }
-  & { currentUser: (
-    { __typename?: 'User' }
-    & Pick<User, 'id' | 'email' | 'name' | 'permissions' | 'interests'>
-  ) }
-);
+export type CurrentUserQuery = { currentUser: Pick<User, 'id' | 'email' | 'name' | 'permissions' | 'interests'> };
 
 export type RequestResetMutationVariables = {
   email: Scalars['String']
 };
 
 
-export type RequestResetMutation = (
-  { __typename?: 'Mutation' }
-  & Pick<Mutation, 'requestReset'>
-);
+export type RequestResetMutation = Pick<Mutation, 'requestReset'>;
 
 export type ResetPasswordMutationVariables = {
   token: Scalars['String'],
@@ -2506,26 +2380,14 @@ export type ResetPasswordMutationVariables = {
 };
 
 
-export type ResetPasswordMutation = (
-  { __typename?: 'Mutation' }
-  & { resetPassword: (
-    { __typename?: 'User' }
-    & Pick<User, 'id' | 'name'>
-  ) }
-);
+export type ResetPasswordMutation = { resetPassword: Pick<User, 'id' | 'name'> };
 
 export type UserDeleteMutationVariables = {
   email: Scalars['String']
 };
 
 
-export type UserDeleteMutation = (
-  { __typename?: 'Mutation' }
-  & { deleteUser: Maybe<(
-    { __typename?: 'User' }
-    & Pick<User, 'id'>
-  )> }
-);
+export type UserDeleteMutation = { deleteUser: Maybe<Pick<User, 'id'>> };
 
 export type UserUpdateMutationVariables = {
   email: Scalars['String'],
@@ -2533,13 +2395,7 @@ export type UserUpdateMutationVariables = {
 };
 
 
-export type UserUpdateMutation = (
-  { __typename?: 'Mutation' }
-  & { updateUser: Maybe<(
-    { __typename?: 'User' }
-    & Pick<User, 'id' | 'name' | 'email' | 'permissions' | 'interests'>
-  )> }
-);
+export type UserUpdateMutation = { updateUser: Maybe<Pick<User, 'id' | 'name' | 'email' | 'permissions' | 'interests'>> };
 
 export const ExerciseContentFragmentDoc = gql`
     fragment exerciseContent on ExerciseInstance {

+ 49 - 8
frontend/src/sortable/components/SortableList.tsx

@@ -7,29 +7,70 @@ import {
 import arrayMove from 'array-move'
 
 const DragHandle = SortableHandle(() => (
-  <span>
-    ::
+  <div className='listitem-drag-handle'>
     <style jsx>{`
-      span {
+      div {
+        width: 20px;
+        height: 15px;
         user-select: none;
+        cursor: row-resize;
+        align-items: center;
+        background: linear-gradient(
+          top,
+          #0006,
+          #0006 20%,
+          #fff0 0,
+          #fff0 40%,
+          #0006 0,
+          #0006 60%,
+          #fff0 0,
+          #fff0 80%,
+          #0006 0,
+          #0006
+        );
+        background: -webkit-linear-gradient(
+          top,
+          #0006,
+          #0006 20%,
+          #fff0 0,
+          #fff0 40%,
+          #0006 0,
+          #0006 60%,
+          #fff0 0,
+          #fff0 80%,
+          #0006 0,
+          #0006
+        );
       }
     `}</style>
-  </span>
+  </div>
 ))
 
 const SortableItem = SortableElement(({ item }: any) => (
   <li>
     <DragHandle />
-    {item}
+    <div className='listitem-content'>{item}</div>
+
+    <style jsx>{`
+      li {
+        display: grid;
+        grid-template-columns: 30px 1fr;
+      }
+    `}</style>
   </li>
 ))
 
 const SortableList = SortableContainer(({ items }: { items: any[] }) => {
   return (
     <ul>
-      {items.map((item: any, index: number) => (
-        <SortableItem key={item} index={index} item={item} />
-      ))}
+      {items.map((item: any, index: number) => {
+        return <SortableItem key={item.key} index={index} item={item} />
+      })}
+      <style jsx>{`
+        ul {
+          padding: 0;
+        }
+      `}</style>
     </ul>
   )
 })

+ 9 - 22
frontend/src/training/components/BlockFormInputs.tsx → frontend/src/training/components/BlockInputs.tsx

@@ -1,23 +1,17 @@
-import { BlockCreateInput } from '../../gql'
+import { BlockContentFragment } from '../../gql'
 import FormatSelector from './FormatSelector'
 import { TextInput } from '../../form'
 
-interface IBlockFormInputs {
+interface IBlockInputs {
   onChange: GenericEventHandler
-  value: BlockCreateInput
+  value: BlockContentFragment
   name: string
 }
 
-const BlockFormInputs = ({ onChange, value, name }: IBlockFormInputs) => {
+const BlockInputs = ({ onChange, value, name }: IBlockInputs) => {
+  console.log('blockinput', name, value)
   return (
     <fieldset>
-      <TextInput
-        name={`${name}.sequence`}
-        label='Sequence'
-        value={value.sequence}
-        type='number'
-        onChange={onChange}
-      />
       <TextInput
         name={`${name}.title`}
         label='Title'
@@ -32,27 +26,20 @@ const BlockFormInputs = ({ onChange, value, name }: IBlockFormInputs) => {
       <TextInput
         name={`${name}.description`}
         label='Description'
-        value={value.description || undefined}
+        value={value.description}
         onChange={onChange}
       />
       <TextInput
         name={`${name}.duration`}
         label='Duration'
-        value={value.duration || undefined}
-        type='number'
-        onChange={onChange}
-      />
-      <TextInput
-        name={`${name}.rounds`}
-        label='Rounds'
-        value={value.rounds || undefined}
+        value={value.duration}
         type='number'
         onChange={onChange}
       />
       <TextInput
         name={`${name}.rest`}
         label='Rest'
-        value={value.rest || undefined}
+        value={value.rest}
         type='number'
         onChange={onChange}
       />
@@ -60,4 +47,4 @@ const BlockFormInputs = ({ onChange, value, name }: IBlockFormInputs) => {
   )
 }
 
-export default BlockFormInputs
+export default BlockInputs

+ 61 - 0
frontend/src/training/components/BlockInstanceInputs.tsx

@@ -0,0 +1,61 @@
+import { useState, useEffect } from 'react'
+import arrayMove from 'array-move'
+import BlockInputs from './BlockInputs'
+import { SortableList } from '../../sortable'
+import { SubBlockFragment } from '../../gql'
+
+const BlockInstanceInputs = ({
+  name,
+  value,
+  onChange
+}: {
+  name: string
+  value: SubBlockFragment[]
+  onChange: GenericEventHandler
+}) => {
+  const [state, setState] = useState(value.map((item, index) => index))
+  console.log(name, value)
+
+  function onSortEnd({
+    oldIndex,
+    newIndex
+  }: {
+    oldIndex: number
+    newIndex: number
+  }) {
+    const newOrder = arrayMove(state, oldIndex, newIndex)
+    setState(newOrder)
+  }
+
+  useEffect(() => {
+    const missingIndices = []
+    for (let idx = 0; idx < value.length - 0; idx++) {
+      if (!state.includes(idx)) missingIndices.push(idx)
+    }
+    setState([...state, ...missingIndices])
+  }, [value])
+
+  const items = state.map((order, index) => {
+    const item = value[order]
+    console.log({ value, order, item })
+    return (
+      <BlockInputs
+        key={item.order}
+        name={`${name}.${index}.block`}
+        value={item.block}
+        onChange={onChange}
+      />
+    )
+  })
+
+  return (
+    <SortableList
+      items={items}
+      onSortEnd={onSortEnd}
+      useDragHandle
+      lockAxis={'y'}
+    />
+  )
+}
+
+export default BlockInstanceInputs

+ 43 - 13
frontend/src/training/components/EditBlock.tsx

@@ -1,20 +1,50 @@
-import { BlockCreateInput } from '../../gql'
-import BlockFormInputs from './BlockFormInputs'
+import { BlockContentFragment } from '../../gql'
+import FormatSelector from './FormatSelector'
+import { TextInput } from '../../form'
 
-const EditBlock = ({
-  onChange,
-  value,
-  name
-}: {
+interface IBlockFormInputs {
   onChange: GenericEventHandler
-  value: BlockCreateInput
+  value: BlockContentFragment
   name: string
-}) => {
+}
+
+const BlockFormInputs = ({ onChange, value, name }: IBlockFormInputs) => {
+  console.log({ value })
   return (
-    <form>
-      <BlockFormInputs name={name} value={value} onChange={onChange} />
-    </form>
+    <fieldset>
+      <TextInput
+        name={`${name}.title`}
+        label='Title'
+        value={value.title}
+        onChange={onChange}
+      />
+      <FormatSelector
+        name={`${name}.format`}
+        value={value.format}
+        onChange={onChange}
+      />
+      <TextInput
+        name={`${name}.description`}
+        label='Description'
+        value={value.description}
+        onChange={onChange}
+      />
+      <TextInput
+        name={`${name}.duration`}
+        label='Duration'
+        value={value.duration}
+        type='number'
+        onChange={onChange}
+      />
+      <TextInput
+        name={`${name}.rest`}
+        label='Rest'
+        value={value.rest}
+        type='number'
+        onChange={onChange}
+      />
+    </fieldset>
   )
 }
 
-export default EditBlock
+export default BlockFormInputs

+ 0 - 19
frontend/src/training/components/EditSubBlocks.tsx

@@ -1,19 +0,0 @@
-import { useState } from 'react'
-import { SortableList } from '../../sortable'
-
-const EditSubBlocks = ({
-  name,
-  value,
-  onChange
-}: {
-  name: string
-  value: any
-  onChange: GenericEventHandler
-}) => {
-  console.log(value)
-  const [state, setState] = useState([1, 2, 3])
-
-  return <SortableList items={state} useDragHandle lockAxis={'y'} />
-}
-
-export default EditSubBlocks

+ 11 - 102
frontend/src/training/components/EditTraining.tsx

@@ -1,120 +1,29 @@
-import {
-  useTrainingQuery,
-  useCreateTrainingMutation,
-  CreateTrainingMutationVariables,
-  BlockInstanceCreateManyInput,
-  TrainingTypeCreateOneInput
-} from '../../gql'
-import { useForm, TextInput, Checkbox, DateTimeInput } from '../../form'
-import TrainingTypeSelector from './TrainingTypeSelector'
-import { parse } from 'date-fns'
-import EditSubBlocks from './EditSubBlocks'
 import { useEffect } from 'react'
-import { trainingDBToArray } from '../utils'
-
-const nextTuesday = parse('11:45', 'HH:mm', new Date())
-nextTuesday.setDate(
-  nextTuesday.getDate() + ((2 + 7 - nextTuesday.getDay()) % 7)
-)
-
-const initialData = {
-  title: '',
-  location: '',
-  attendance: 0,
-  trainingDate: nextTuesday.toISOString(),
-  type: { connect: { id: '' } } as TrainingTypeCreateOneInput,
-  blocks: {
-    connect: [],
-    create: []
-  } as BlockInstanceCreateManyInput,
-  published: false
-} as CreateTrainingMutationVariables
+import { useTrainingQuery, useCreateTrainingMutation } from '../../gql'
+import { useForm } from '../../form'
+import { emptyTraining } from '../constants'
+import TrainingInputs from './TrainingInputs'
 
 const EditTraining = ({ id = '' }: { id?: string }) => {
   const trainingData = useTrainingQuery({ variables: { id } })
-  trainingDBToArray(trainingData.data?.training)
 
-  const { values, touched, onChange, loadData } = useForm(initialData)
+  const { values, touched, onChange, loadData } = useForm(emptyTraining)
   const [createTraining, createData] = useCreateTrainingMutation()
-  const props = { onChange }
 
-  // useEffect(() => {
-  //   if (trainingData.data && trainingData.data.training)
-  //     loadData(trainingData.data.training)
-  // }, [trainingData])
-  // console.log(data)
+  useEffect(() => {
+    console.log('training effect', trainingData)
+    if (trainingData.data?.training) loadData(trainingData.data.training)
+  }, [trainingData.data])
 
   return (
     <>
       <form
         onSubmit={ev => {
           ev.preventDefault()
-
-          createTraining({ variables: values })
+          //createTraining({ variables: values })
         }}
       >
-        <TextInput name='title' label='Title' value={values.title} {...props} />
-        <TrainingTypeSelector value={values.type} {...props} />
-        <DateTimeInput
-          name='trainingDate'
-          label='Training date'
-          value={values.trainingDate}
-          {...props}
-        />
-        <TextInput
-          name='location'
-          label='Location'
-          value={values.location}
-          {...props}
-        />
-        <TextInput
-          name='attendance'
-          label='Attendance'
-          type='number'
-          value={values.attendance}
-          {...props}
-        />
-        <Checkbox
-          name='published'
-          label='Published'
-          value={values.published}
-          {...props}
-        />
-        <label>Blocks</label>
-        <EditSubBlocks name='blocks' value={values.blocks} {...props} />
-        {/*values.blocks &&
-          values.blocks.map((block, index) => (
-            <BlockFormInputs
-              key={index}
-              name='blocks'
-              //label='Blocks'
-              //name={`blocks.${index}`}
-              value={block}
-              {...props}
-            />
-          ))*/}
-        <button
-          onClick={event => {
-            event.preventDefault()
-            const { onChange } = props
-            onChange({
-              target: {
-                type: 'custom',
-                name: 'blocks',
-                value: {
-                  create:
-                    values.blocks && values.blocks.create
-                      ? [...values.blocks.create]
-                      : [{ title: '' }],
-                  connect: []
-                }
-              }
-            })
-          }}
-          type='button'
-        >
-          Add block
-        </button>
+        <TrainingInputs name='test' values={values} onChange={onChange} />
         <button type='submit' disabled={createData.loading}>
           Save
         </button>

+ 19 - 18
frontend/src/training/components/FormatSelector.tsx

@@ -1,32 +1,34 @@
-import { useFormatsQuery, FormatCreateOneInput } from '../../gql'
+import { useFormatsQuery, FormatCreateOneInput, Format } from '../../gql'
+import { useEffect } from 'react'
 
 interface IFormatSelector {
-  value?: FormatCreateOneInput
+  value: Format
+  onChange: GenericEventHandler
   name?: string
   label?: string
-  onChange: GenericEventHandler
 }
 
 const FormatSelector = ({
-  onChange,
   value,
+  onChange,
   name = 'format',
-  label = 'Format',
-  ...props
+  label = 'Format'
 }: IFormatSelector) => {
   const formats = useFormatsQuery()
-  const id = value && value.connect && value.connect.id
+  const { id } = value
 
-  if (formats.data && formats.data.formats.length > 0 && !id) {
-    const id = formats.data.formats[0].id
-    onChange({
-      target: {
-        type: 'custom',
-        value: { connect: { id } },
-        name
-      }
-    })
-  }
+  useEffect(() => {
+    if (formats.data && formats.data.formats.length > 0 && !id) {
+      console.log('format:', name)
+      onChange({
+        target: {
+          type: 'custom',
+          value: formats.data.formats[0],
+          name
+        }
+      })
+    }
+  }, [formats])
 
   return (
     <>
@@ -45,7 +47,6 @@ const FormatSelector = ({
           }
           onChange(copy)
         }}
-        {...props}
       >
         {formats.loading && 'loading formats...'}
         {formats.error && 'error loading formats'}

+ 87 - 0
frontend/src/training/components/TrainingInputs.tsx

@@ -0,0 +1,87 @@
+import { TextInput, DateTimeInput, Checkbox } from '../../form'
+import TrainingTypeSelector from './TrainingTypeSelector'
+import BlockInstanceInputs from './BlockInstanceInputs'
+import { TrainingQuery } from '../../gql'
+import { emptyBlockInstance } from '../constants'
+
+const TrainingInputs = ({
+  values,
+  onChange,
+  name = ''
+}: {
+  values: NonNullable<TrainingQuery['training']>
+  name: string
+  onChange: GenericEventHandler
+}) => {
+  return (
+    <>
+      <TextInput
+        name='title'
+        label='Title'
+        value={values.title}
+        onChange={onChange}
+      />
+      <TrainingTypeSelector
+        name='type'
+        value={values.type}
+        onChange={onChange}
+      />
+      <DateTimeInput
+        name='trainingDate'
+        label='Training date'
+        value={values.trainingDate}
+        onChange={onChange}
+      />
+      <TextInput
+        name='location'
+        label='Location'
+        value={values.location}
+        onChange={onChange}
+      />
+      <TextInput
+        name='attendance'
+        label='Attendance'
+        type='number'
+        value={values.attendance}
+        onChange={onChange}
+      />
+      <Checkbox
+        name='published'
+        label='Published'
+        value={values.published}
+        onChange={onChange}
+      />
+      <label>Blocks</label>
+      {values.blocks && (
+        <BlockInstanceInputs
+          name='blocks'
+          value={values.blocks}
+          onChange={onChange}
+        />
+      )}
+      <button
+        onClick={event => {
+          event.preventDefault()
+          console.log('add')
+          onChange({
+            target: {
+              type: 'custom',
+              name: 'blocks',
+              value: values.blocks
+                ? [
+                    ...values.blocks,
+                    { ...emptyBlockInstance, order: values.blocks.length - 1 }
+                  ]
+                : [{ ...emptyBlockInstance, order: 0 }]
+            }
+          })
+        }}
+        type='button'
+      >
+        Add block
+      </button>
+    </>
+  )
+}
+
+export default TrainingInputs

+ 21 - 25
frontend/src/training/components/TrainingTypeSelector.tsx

@@ -1,40 +1,37 @@
-import { useTrainingTypesQuery, TrainingTypeCreateOneInput } from '../../gql'
+import { useTrainingTypesQuery, TrainingType } from '../../gql'
 import { Modal } from '../../modal'
 import AddTrainingType from './AddTrainingType'
-import { useState } from 'react'
+import { useState, useEffect } from 'react'
 
 interface ITrainingTypeSelector {
-  value?: TrainingTypeCreateOneInput
-  label?: string
-  name?: string
+  value: TrainingType
   onChange: GenericEventHandler
+  name?: string
+  label?: string
 }
 
 const TrainingTypeSelector = ({
-  onChange,
   value,
+  onChange,
   name = 'type',
-  label = 'Training type',
-  ...props
+  label = 'Training type'
 }: ITrainingTypeSelector) => {
   const [modalState, setModalState] = useState(false)
   const trainingTypes = useTrainingTypesQuery()
-  const id = value && value.connect && value.connect.id
+  const id = value?.id
 
-  if (
-    trainingTypes.data &&
-    trainingTypes.data.trainingTypes.length > 0 &&
-    !id
-  ) {
-    const id = trainingTypes.data.trainingTypes[0].id
-    onChange({
-      target: {
-        type: 'custom',
-        value: { connect: { id } },
-        name
-      }
-    })
-  }
+  useEffect(() => {
+    if (trainingTypes.data && trainingTypes.data.trainingTypes.length > 0) {
+      console.log('tt:', name)
+      onChange({
+        target: {
+          type: 'custom',
+          value: trainingTypes.data.trainingTypes[0],
+          name
+        }
+      })
+    }
+  }, [trainingTypes])
 
   return (
     <>
@@ -42,7 +39,7 @@ const TrainingTypeSelector = ({
       <select
         id={name}
         name={name}
-        value={id || undefined}
+        value={id}
         onChange={event => {
           const copy: CustomChangeEvent = {
             target: {
@@ -53,7 +50,6 @@ const TrainingTypeSelector = ({
           }
           onChange(copy)
         }}
-        {...props}
       >
         {trainingTypes.loading && 'loading training types...'}
         {trainingTypes.error && 'error loading training types'}

+ 30 - 0
frontend/src/training/constants.ts

@@ -0,0 +1,30 @@
+import { nextTime } from './utils'
+import { SubBlockFragment, TrainingQuery, BlockContentFragment } from '../gql'
+
+export const emptyBlock: BlockContentFragment = {
+  id: '',
+  title: '',
+  format: { id: '', name: '', description: '' },
+  videos: [],
+  pictures: [],
+  blocks: [],
+  exercises: []
+}
+
+export const emptyBlockInstance: SubBlockFragment = {
+  id: '',
+  order: 0,
+  block: emptyBlock
+}
+
+export const emptyTraining: NonNullable<TrainingQuery['training']> = {
+  id: '',
+  createdAt: '',
+  title: '',
+  location: '',
+  attendance: 0,
+  trainingDate: nextTime('Tuesday', '11:45'),
+  type: { id: '', name: '', description: '' },
+  blocks: [] as SubBlockFragment[],
+  published: false
+}

+ 26 - 9
frontend/src/training/utils.ts

@@ -1,12 +1,6 @@
+import { parse } from 'date-fns'
 import { IBlock, IExercise, IRating } from './types'
-import {
-  Training,
-  TrainingType,
-  TrainingQuery,
-  TrainingQueryResult,
-  TrainingQueryVariables,
-  SubBlockFragment
-} from '../gql'
+import { TrainingQuery, SubBlockFragment } from '../gql'
 
 /**
  * Takes a block of exercises and calculates the duration in seconds.
@@ -69,7 +63,7 @@ export function calculateRating(ratings: IRating[]) {
 export function trainingDBToArray(DBTraining: TrainingQuery['training']) {
   console.log({ DBTraining })
   if (!DBTraining) return undefined
-  const { __typename, blocks, ...data } = DBTraining
+  const { blocks, ...data } = DBTraining
   return { ...data, blocks: blockDBToArray(blocks) }
 }
 
@@ -82,3 +76,26 @@ export function blockDBToArray(DBSubBlock?: SubBlockFragment[]) {
 }
 
 export function blockArrayToDB() {}
+
+const Weekdays = {
+  Monday: 1,
+  Tuesday: 2,
+  Wednesday: 3,
+  Thursday: 4,
+  Friday: 5,
+  Saturday: 6,
+  Sunday: 0
+}
+type TWeekdays = keyof typeof Weekdays
+/**
+ * Get the date of the next weekday.
+ * @param weekDay Sunday=0, Monday=1, Tuesday=2
+ * @param time Format: HH:mm
+ */
+export function nextTime(weekDay: TWeekdays, time: string) {
+  const nextTime = parse(time, 'HH:mm', new Date())
+  nextTime.setDate(
+    nextTime.getDate() + ((Weekdays[weekDay] + 7 - nextTime.getDay()) % 7)
+  )
+  return nextTime.toISOString()
+}