From 949846f9d07aa5bcff7d6088232e5cac8808f3de Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 20 Nov 2018 15:17:53 +0100 Subject: [PATCH 1/8] Move objective group files to own folder --- .../{modules => objective-groups}/ObjectiveGroup.vue | 0 .../{modules => objective-groups}/ObjectiveGroupControl.vue | 0 .../{modules => objective-groups}/ObjectiveGroups.vue | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename client/src/components/{modules => objective-groups}/ObjectiveGroup.vue (100%) rename client/src/components/{modules => objective-groups}/ObjectiveGroupControl.vue (100%) rename client/src/components/{modules => objective-groups}/ObjectiveGroups.vue (91%) diff --git a/client/src/components/modules/ObjectiveGroup.vue b/client/src/components/objective-groups/ObjectiveGroup.vue similarity index 100% rename from client/src/components/modules/ObjectiveGroup.vue rename to client/src/components/objective-groups/ObjectiveGroup.vue diff --git a/client/src/components/modules/ObjectiveGroupControl.vue b/client/src/components/objective-groups/ObjectiveGroupControl.vue similarity index 100% rename from client/src/components/modules/ObjectiveGroupControl.vue rename to client/src/components/objective-groups/ObjectiveGroupControl.vue diff --git a/client/src/components/modules/ObjectiveGroups.vue b/client/src/components/objective-groups/ObjectiveGroups.vue similarity index 91% rename from client/src/components/modules/ObjectiveGroups.vue rename to client/src/components/objective-groups/ObjectiveGroups.vue index 9a71c673..56366d28 100644 --- a/client/src/components/modules/ObjectiveGroups.vue +++ b/client/src/components/objective-groups/ObjectiveGroups.vue @@ -15,8 +15,8 @@ + + diff --git a/client/src/components/modules/Module.vue b/client/src/components/modules/Module.vue index d12ed23c..35537639 100644 --- a/client/src/components/modules/Module.vue +++ b/client/src/components/modules/Module.vue @@ -10,19 +10,25 @@

Lernziele

+ + +

Alles klar?

- - + + + + diff --git a/client/src/components/objective-groups/ObjectiveForm.vue b/client/src/components/objective-groups/ObjectiveForm.vue new file mode 100644 index 00000000..b1c5bc62 --- /dev/null +++ b/client/src/components/objective-groups/ObjectiveForm.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/client/src/store/index.js b/client/src/store/index.js index 50899556..759dbd75 100644 --- a/client/src/store/index.js +++ b/client/src/store/index.js @@ -14,7 +14,9 @@ export default new Vuex.Store({ filterForSchoolClass: '', currentContentBlock: '', currentRoomEntry: '', - parentRoom: null + parentRoom: null, + parentModule: '', + objectiveGroupType: '' }, getters: {}, @@ -23,9 +25,18 @@ export default new Vuex.Store({ setSpecialContainerClass({commit}, payload) { commit('setSpecialContainerClass', payload); }, - hideModal({commit}) { + hideModal({commit, dispatch}) { document.body.classList.remove('no-scroll'); // won't get at the body any other way commit('setModal', false); + dispatch('resetModalState'); + }, + resetModalState({commit}) { + commit('setCurrentRoomEntry', ''); + commit('setCurrentContentBlock', ''); + commit('setContentBlockPosition', {}); + commit('setParentRoom', null); + commit('setParentModule', ''); + commit('setObjectiveGroupType', ''); }, resetContentBlockPosition({commit}) { commit('setContentBlockPosition', {}); @@ -49,6 +60,11 @@ export default new Vuex.Store({ commit('setCurrentRoomEntry', payload); dispatch('showModal', 'edit-room-entry-wizard'); }, + addObjectiveGroup({commit, dispatch}, {module, type}) { + commit('setParentModule', module); + commit('setObjectiveGroupType', type); + dispatch('showModal', 'new-objective-group-wizard'); + }, showModal({commit}, payload) { document.body.classList.add('no-scroll'); // won't get at the body any other way commit('setModal', payload); @@ -85,6 +101,12 @@ export default new Vuex.Store({ }, setCurrentRoomEntry(state, payload) { state.currentRoomEntry = payload; + }, + setParentModule(state, payload) { + state.parentModule = payload; + }, + setObjectiveGroupType(state, payload) { + state.objectiveGroupType = payload; } } }) From 8d9cb86ae8a4181c8f43d77c055825791525fe20 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 20 Nov 2018 15:20:02 +0100 Subject: [PATCH 3/8] Move style for icon button --- .../content-block-form/ContentsForm.vue | 15 ++------------- client/src/styles/_buttons.scss | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/client/src/components/content-block-form/ContentsForm.vue b/client/src/components/content-block-form/ContentsForm.vue index b873590e..7fd3644e 100644 --- a/client/src/components/content-block-form/ContentsForm.vue +++ b/client/src/components/content-block-form/ContentsForm.vue @@ -14,7 +14,6 @@ @input="setContentBlockType" class="contents-form__task" > - - + + class="contents-form__trash-icon icon-button__icon"> Date: Tue, 20 Nov 2018 17:28:19 +0100 Subject: [PATCH 4/8] Add objective group mutation --- .../gql/mutations/addObjectiveGroup.gql | 16 ++++++++++++ server/objectives/inputs.py | 16 ++++++++++++ server/objectives/mutations.py | 26 ++++++++++++++++++- 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 client/src/graphql/gql/mutations/addObjectiveGroup.gql create mode 100644 server/objectives/inputs.py diff --git a/client/src/graphql/gql/mutations/addObjectiveGroup.gql b/client/src/graphql/gql/mutations/addObjectiveGroup.gql new file mode 100644 index 00000000..69bf95dd --- /dev/null +++ b/client/src/graphql/gql/mutations/addObjectiveGroup.gql @@ -0,0 +1,16 @@ +#import "../fragments/objectiveGroupParts.gql" +mutation AddObjectiveGroup($input: AddObjectiveGroupInput!) { + addObjectiveGroup(input: $input) { + objectiveGroup { + ...ObjectiveGroupParts + objectives { + edges { + node { + id + text + } + } + } + } + } +} diff --git a/server/objectives/inputs.py b/server/objectives/inputs.py new file mode 100644 index 00000000..cd4a96cf --- /dev/null +++ b/server/objectives/inputs.py @@ -0,0 +1,16 @@ +import graphene +from graphene import InputObjectType + + +class ObjectiveInput(InputObjectType): + text = graphene.String(required=True) + + +class ObjectiveGroupInput(InputObjectType): + title = graphene.String(required=True) + module = graphene.ID(required=True) + objectives = graphene.List(ObjectiveInput) + + +class AddObjectiveGroupArgument(ObjectiveGroupInput): + pass diff --git a/server/objectives/mutations.py b/server/objectives/mutations.py index f88c6c10..ee27bcd6 100644 --- a/server/objectives/mutations.py +++ b/server/objectives/mutations.py @@ -1,8 +1,10 @@ import graphene from graphene import relay, InputObjectType from api.utils import get_object +from books.models import Module from books.schema.inputs import UserGroupBlockVisibility from core.utils import set_visible_for, set_hidden_for +from objectives.inputs import AddObjectiveGroupArgument from objectives.models import ObjectiveProgressStatus, Objective, ObjectiveGroup from objectives.schema import ObjectiveNode, ObjectiveGroupNode @@ -52,10 +54,32 @@ class UpdateObjectiveGroupVisibility(relay.ClientIDMutation): objective_group.save() - return cls(objective_group=objective_group) +class AddObjectiveGroup(relay.ClientIDMutation): + class Input: + objective_group = graphene.Argument(AddObjectiveGroupArgument) + + objective_group = graphene.Field(ObjectiveGroupNode) + + @classmethod + def mutate_and_get_payload(cls, root, info, **kwargs): + objective_group_data = kwargs.get('objective_group') + title = objective_group_data.get('title') + if title != 'society': + title = 'language_communication' + module_id = objective_group_data.get('module') + module = get_object(Module, module_id) + owner = info.context.user + new_objective_group = ObjectiveGroup.objects.create(title=title, module=module, owner=owner) + objectives = objective_group_data.get('objectives') + for objective in objectives: + Objective.objects.create(text=objective.get('text'), group=new_objective_group) + return cls(objective_group=new_objective_group) + + class ObjectiveMutations: update_objective_progress = UpdateObjectiveProgress.Field() update_objective_group_visibility = UpdateObjectiveGroupVisibility.Field() + add_objective_group = AddObjectiveGroup.Field() From a89c0d006b97a4534b661b13d98b1fb827df77da Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 20 Nov 2018 18:48:48 +0100 Subject: [PATCH 5/8] Save objective group via mutation --- .../NewObjectiveGroupWizard.vue | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/client/src/components/objective-groups/NewObjectiveGroupWizard.vue b/client/src/components/objective-groups/NewObjectiveGroupWizard.vue index 92ac1f8a..84497951 100644 --- a/client/src/components/objective-groups/NewObjectiveGroupWizard.vue +++ b/client/src/components/objective-groups/NewObjectiveGroupWizard.vue @@ -3,7 +3,7 @@ - + { - try { - const query = ROOM_ENTRIES_QUERY; - const variables = {slug: this.room.slug}; - const data = store.readQuery({query, variables}); - if (data.room && data.room.roomEntries) { - data.room.roomEntries.edges.unshift({ - node: roomEntry, - __typename: 'RoomEntryNode' - }); - store.writeQuery({query, variables, data}); - } - } catch (e) { - // Query did not exist in the cache, and apollo throws a generic Error. Do nothing + // todo: make update work + // update: (store, {data: {addObjectiveGroup: {objectiveGroup}}}) => { + // const query = MODULE_DETAILS_QUERY; + // const variables = {slug: this.$route.params.slug}; + // const data = store.readQuery({query, variables}); + // debugger; + // if (data.module && data.module.objectiveGroups) { + // data.module.objectiveGroups.edges.push({ + // node: objectiveGroup, + // __typename: 'ObjectiveGroupNode' + // }); + // store.writeQuery({query, variables, data}); + // } + // + // } + refetchQueries: [{ + query: MODULE_DETAILS_QUERY, + variables: { + slug: this.$route.params.slug } - } + }] + }).then(() => { this.hideModal(); }); From 9a6b9bf3e35f98218bb6cb2fdd62886ee7b8ffeb Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 21 Nov 2018 14:56:43 +0100 Subject: [PATCH 6/8] Add edit objective form wizard to client --- client/src/App.vue | 4 +- .../EditObjectiveGroupWizard.vue | 107 ++++++++++++++++++ .../NewObjectiveGroupWizard.vue | 47 ++------ .../objective-groups/ObjectiveForm.vue | 6 +- .../objective-groups/ObjectiveGroup.vue | 14 ++- .../objective-groups/ObjectiveGroupForm.vue | 64 +++++++++++ client/src/graphql/client.js | 3 +- .../gql/fragments/objectiveGroupParts.gql | 1 + .../gql/mutations/updateObjectiveGroup.gql | 16 +++ .../src/graphql/gql/objectiveGroupQuery.gql | 14 +++ client/src/store/index.js | 11 +- 11 files changed, 245 insertions(+), 42 deletions(-) create mode 100644 client/src/components/objective-groups/EditObjectiveGroupWizard.vue create mode 100644 client/src/components/objective-groups/ObjectiveGroupForm.vue create mode 100644 client/src/graphql/gql/mutations/updateObjectiveGroup.gql create mode 100644 client/src/graphql/gql/objectiveGroupQuery.gql diff --git a/client/src/App.vue b/client/src/App.vue index 9e17d019..e78c6e57 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -15,6 +15,7 @@ import NewRoomEntryWizard from '@/components/rooms/room-entries/NewRoomEntryWizard'; import EditRoomEntryWizard from '@/components/rooms/room-entries/EditRoomEntryWizard'; import NewObjectiveGroupWizard from '@/components/objective-groups/NewObjectiveGroupWizard'; + import EditObjectiveGroupWizard from '@/components/objective-groups/EditObjectiveGroupWizard'; export default { name: 'App', @@ -28,7 +29,8 @@ EditContentBlockWizard, NewRoomEntryWizard, EditRoomEntryWizard, - NewObjectiveGroupWizard + NewObjectiveGroupWizard, + EditObjectiveGroupWizard }, computed: { diff --git a/client/src/components/objective-groups/EditObjectiveGroupWizard.vue b/client/src/components/objective-groups/EditObjectiveGroupWizard.vue new file mode 100644 index 00000000..9e67b362 --- /dev/null +++ b/client/src/components/objective-groups/EditObjectiveGroupWizard.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/client/src/components/objective-groups/NewObjectiveGroupWizard.vue b/client/src/components/objective-groups/NewObjectiveGroupWizard.vue index 84497951..a09c69f9 100644 --- a/client/src/components/objective-groups/NewObjectiveGroupWizard.vue +++ b/client/src/components/objective-groups/NewObjectiveGroupWizard.vue @@ -1,27 +1,15 @@ diff --git a/client/src/components/objective-groups/ObjectiveForm.vue b/client/src/components/objective-groups/ObjectiveForm.vue index b1c5bc62..a2e94d15 100644 --- a/client/src/components/objective-groups/ObjectiveForm.vue +++ b/client/src/components/objective-groups/ObjectiveForm.vue @@ -2,10 +2,10 @@ @@ -14,7 +14,7 @@ diff --git a/client/src/components/objective-groups/ObjectiveGroupForm.vue b/client/src/components/objective-groups/ObjectiveGroupForm.vue new file mode 100644 index 00000000..b32ca67e --- /dev/null +++ b/client/src/components/objective-groups/ObjectiveGroupForm.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/client/src/graphql/client.js b/client/src/graphql/client.js index 48b7f090..c7031b1a 100644 --- a/client/src/graphql/client.js +++ b/client/src/graphql/client.js @@ -44,7 +44,8 @@ const cache = new InMemoryCache({ contentBlock: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ContentBlockNode', id: args.id}), chapter: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ChapterNode', id: args.id}), assignment: (_, args, {getCacheKey}) => getCacheKey({__typename: 'AssignmentNode', id: args.id}), - objective: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveNode', id: args.id}) + objective: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveNode', id: args.id}), + objectiveGroup: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveGroupNode', id: args.id}) } } }); diff --git a/client/src/graphql/gql/fragments/objectiveGroupParts.gql b/client/src/graphql/gql/fragments/objectiveGroupParts.gql index 38e6f8b7..a806919a 100644 --- a/client/src/graphql/gql/fragments/objectiveGroupParts.gql +++ b/client/src/graphql/gql/fragments/objectiveGroupParts.gql @@ -2,6 +2,7 @@ fragment ObjectiveGroupParts on ObjectiveGroupNode { id title displayTitle + mine owner { id } diff --git a/client/src/graphql/gql/mutations/updateObjectiveGroup.gql b/client/src/graphql/gql/mutations/updateObjectiveGroup.gql new file mode 100644 index 00000000..4a0bfa68 --- /dev/null +++ b/client/src/graphql/gql/mutations/updateObjectiveGroup.gql @@ -0,0 +1,16 @@ +#import "../fragments/objectiveGroupParts.gql" +mutation UpdateObjectiveGroup($input: UpdateObjectiveGroupInput!) { + updateObjectiveGroup(input: $input) { + objectiveGroup { + ...ObjectiveGroupParts + objectives { + edges { + node { + id + text + } + } + } + } + } +} diff --git a/client/src/graphql/gql/objectiveGroupQuery.gql b/client/src/graphql/gql/objectiveGroupQuery.gql new file mode 100644 index 00000000..c4b6b653 --- /dev/null +++ b/client/src/graphql/gql/objectiveGroupQuery.gql @@ -0,0 +1,14 @@ +#import "./fragments/objectiveGroupParts.gql" +query ObjectiveGroupQuery($id: ID!) { + objectiveGroup(id: $id) { + ...ObjectiveGroupParts + objectives { + edges { + node { + id + text + } + } + } + } +} diff --git a/client/src/store/index.js b/client/src/store/index.js index 759dbd75..5950370a 100644 --- a/client/src/store/index.js +++ b/client/src/store/index.js @@ -16,7 +16,8 @@ export default new Vuex.Store({ currentRoomEntry: '', parentRoom: null, parentModule: '', - objectiveGroupType: '' + objectiveGroupType: '', + currentObjectiveGroup: '' }, getters: {}, @@ -37,6 +38,7 @@ export default new Vuex.Store({ commit('setParentRoom', null); commit('setParentModule', ''); commit('setObjectiveGroupType', ''); + commit('setCurrentObjectiveGroup', ''); }, resetContentBlockPosition({commit}) { commit('setContentBlockPosition', {}); @@ -65,6 +67,10 @@ export default new Vuex.Store({ commit('setObjectiveGroupType', type); dispatch('showModal', 'new-objective-group-wizard'); }, + editObjectiveGroup({commit, dispatch}, payload) { + commit('setCurrentObjectiveGroup', payload); + dispatch('showModal', 'edit-objective-group-wizard'); + }, showModal({commit}, payload) { document.body.classList.add('no-scroll'); // won't get at the body any other way commit('setModal', payload); @@ -107,6 +113,9 @@ export default new Vuex.Store({ }, setObjectiveGroupType(state, payload) { state.objectiveGroupType = payload; + }, + setCurrentObjectiveGroup(state, payload) { + state.currentObjectiveGroup = payload; } } }) From 7fd772871256d8f47165e61e723747442a3beaab Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 21 Nov 2018 14:57:36 +0100 Subject: [PATCH 7/8] Add mutation to server --- server/objectives/inputs.py | 8 +++++--- server/objectives/mutations.py | 36 +++++++++++++++++++++++++++++++++- server/objectives/schema.py | 4 ++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/server/objectives/inputs.py b/server/objectives/inputs.py index cd4a96cf..2c08ea01 100644 --- a/server/objectives/inputs.py +++ b/server/objectives/inputs.py @@ -4,13 +4,15 @@ from graphene import InputObjectType class ObjectiveInput(InputObjectType): text = graphene.String(required=True) + id = graphene.ID() -class ObjectiveGroupInput(InputObjectType): +class AddObjectiveGroupArgument(InputObjectType): title = graphene.String(required=True) module = graphene.ID(required=True) objectives = graphene.List(ObjectiveInput) -class AddObjectiveGroupArgument(ObjectiveGroupInput): - pass +class UpdateObjectiveGroupArgument(InputObjectType): + id = graphene.ID(required=True) + objectives = graphene.List(ObjectiveInput) diff --git a/server/objectives/mutations.py b/server/objectives/mutations.py index ee27bcd6..df9e50ff 100644 --- a/server/objectives/mutations.py +++ b/server/objectives/mutations.py @@ -1,10 +1,12 @@ import graphene from graphene import relay, InputObjectType +from graphql_relay import from_global_id + from api.utils import get_object from books.models import Module from books.schema.inputs import UserGroupBlockVisibility from core.utils import set_visible_for, set_hidden_for -from objectives.inputs import AddObjectiveGroupArgument +from objectives.inputs import AddObjectiveGroupArgument, UpdateObjectiveGroupArgument from objectives.models import ObjectiveProgressStatus, Objective, ObjectiveGroup from objectives.schema import ObjectiveNode, ObjectiveGroupNode @@ -79,7 +81,39 @@ class AddObjectiveGroup(relay.ClientIDMutation): return cls(objective_group=new_objective_group) +class UpdateObjectiveGroup(relay.ClientIDMutation): + class Input: + objective_group = graphene.Argument(UpdateObjectiveGroupArgument) + + objective_group = graphene.Field(ObjectiveGroupNode) + + @classmethod + def mutate_and_get_payload(cls, root, info, **kwargs): + objective_group_data = kwargs.get('objective_group') + id = objective_group_data.get('id') + objective_group = get_object(ObjectiveGroup, id) + objectives = objective_group_data.get('objectives') + existing_objective_ids = list(objective_group.objectives.values_list('id', flat=True)) + for objective in objectives: + if objective.get('id') is not None: + objective_id = objective.get('id') + updated_objective = get_object(Objective, objective_id) + updated_objective.text = objective.get('text') + updated_objective.save() + existing_objective_ids.remove(int(from_global_id(objective_id)[1])) + else: + Objective.objects.create(text=objective.get('text'), group=objective_group) + + # remove existing items that are not in payload + for objective_id in existing_objective_ids: + objective = Objective.objects.get(pk=objective_id) + objective.delete() + + return cls(objective_group=objective_group) + + class ObjectiveMutations: update_objective_progress = UpdateObjectiveProgress.Field() update_objective_group_visibility = UpdateObjectiveGroupVisibility.Field() add_objective_group = AddObjectiveGroup.Field() + update_objective_group = UpdateObjectiveGroup.Field() diff --git a/server/objectives/schema.py b/server/objectives/schema.py index cef4e1dd..0c6f136a 100644 --- a/server/objectives/schema.py +++ b/server/objectives/schema.py @@ -9,6 +9,7 @@ from objectives.models import ObjectiveGroup, Objective, ObjectiveProgressStatus class ObjectiveGroupNode(DjangoObjectType): pk = graphene.Int() display_title = graphene.String() + mine = graphene.Boolean() class Meta: model = ObjectiveGroup @@ -21,6 +22,9 @@ class ObjectiveGroupNode(DjangoObjectType): def resolve_display_title(self, *args, **kwargs): return self.get_title_display() + def resolve_mine(self, info, **kwargs): + return self.owner is not None and self.owner.pk == info.context.user.pk + class ObjectiveNode(DjangoObjectType): From 4bf3f5164450a362fd6abce00ebd886efe33ec4b Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 21 Nov 2018 15:18:54 +0100 Subject: [PATCH 8/8] Apply sorting of objective groups to students as well --- client/src/components/modules/Module.vue | 2 +- client/src/components/objective-groups/ObjectiveGroups.vue | 3 ++- client/src/helpers/sorting.js | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 client/src/helpers/sorting.js diff --git a/client/src/components/modules/Module.vue b/client/src/components/modules/Module.vue index 35537639..1c963084 100644 --- a/client/src/components/modules/Module.vue +++ b/client/src/components/modules/Module.vue @@ -33,7 +33,7 @@ import UPDATE_OBJECTIVE_PROGRESS_MUTATION from '@/graphql/gql/mutations/updateObjectiveProgress.gql'; import OBJECTIVE_QUERY from '@/graphql/gql/objectiveQuery.gql'; - const withoutOwnerFirst = (a, b) => a.owner ? 1 : 0; + import {withoutOwnerFirst} from '@/helpers/sorting'; export default { components: { diff --git a/client/src/components/objective-groups/ObjectiveGroups.vue b/client/src/components/objective-groups/ObjectiveGroups.vue index 56366d28..98c53d37 100644 --- a/client/src/components/objective-groups/ObjectiveGroups.vue +++ b/client/src/components/objective-groups/ObjectiveGroups.vue @@ -18,6 +18,7 @@ import ObjectiveGroup from '@/components/objective-groups/ObjectiveGroup'; import ObjectiveGroupControl from '@/components/objective-groups/ObjectiveGroupControl'; import {meQuery} from '@/graphql/queries'; + import {withoutOwnerFirst} from '@/helpers/sorting'; export default { props: { @@ -55,7 +56,7 @@ return this.groups; } else { // todo: maybe this can be done a bit more elegantly - const groups = [...this.groups]; + const groups = [...this.groups].sort(withoutOwnerFirst); const objectives = groups.map(g => g.objectives).flat(); // get all objectives in one array const firstGroup = Object.assign({}, groups.shift(), {objectives}); diff --git a/client/src/helpers/sorting.js b/client/src/helpers/sorting.js new file mode 100644 index 00000000..9e3db38e --- /dev/null +++ b/client/src/helpers/sorting.js @@ -0,0 +1 @@ +export const withoutOwnerFirst = (a, b) => a.owner ? 1 : -1;