Skip to content

Commit

Permalink
added new section to sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
asliayk committed Dec 16, 2024
1 parent 5449cc4 commit 4e30d18
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
(onBrowsePressed)="openChannelOverviewDialog()"
(onDirectChatPressed)="openCreateOneToOneChatDialog()"
(onGroupChatPressed)="openCreateGroupChatDialog()"
[showAddOption]="CHANNEL_TYPE_SHOW_ADD_OPTION"
[channelTypeIcon]="CHANNEL_TYPE_ICON"
[sidebarItemAlwaysShow]="DEFAULT_SHOW_ALWAYS"
[collapseState]="DEFAULT_COLLAPSE_STATE"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { PageType, SortDirection } from 'app/shared/metis/metis.util';
import {
faBan,
faBookmark,
faClock,
faComment,
faComments,
faFile,
Expand All @@ -27,7 +28,7 @@ import {
} from '@fortawesome/free-solid-svg-icons';
import { ButtonType } from 'app/shared/components/button.component';
import { CourseWideSearchComponent, CourseWideSearchConfig } from 'app/overview/course-conversations/course-wide-search/course-wide-search.component';
import { AccordionGroups, ChannelAccordionShowAdd, ChannelTypeIcons, CollapseState, SidebarCardElement, SidebarData, SidebarItemShowAlways } from 'app/types/sidebar';
import { AccordionGroups, ChannelTypeIcons, CollapseState, SidebarCardElement, SidebarData, SidebarItemShowAlways } from 'app/types/sidebar';
import { CourseOverviewService } from 'app/overview/course-overview.service';
import { GroupChatCreateDialogComponent } from 'app/overview/course-conversations/dialogs/group-chat-create-dialog/group-chat-create-dialog.component';
import { defaultFirstLayerDialogOptions, defaultSecondLayerDialogOptions } from 'app/overview/course-conversations/other/conversation.util';
Expand All @@ -44,6 +45,7 @@ import { canCreateChannel } from 'app/shared/metis/conversations/conversation-pe

const DEFAULT_CHANNEL_GROUPS: AccordionGroups = {
favoriteChannels: { entityData: [] },
recently: { entityData: [] },
generalChannels: { entityData: [] },
exerciseChannels: { entityData: [] },
lectureChannels: { entityData: [] },
Expand All @@ -52,18 +54,6 @@ const DEFAULT_CHANNEL_GROUPS: AccordionGroups = {
savedPosts: { entityData: [] },
};

const CHANNEL_TYPE_SHOW_ADD_OPTION: ChannelAccordionShowAdd = {
generalChannels: true,
exerciseChannels: true,
examChannels: true,
groupChats: true,
directMessages: true,
favoriteChannels: false,
lectureChannels: true,
hiddenChannels: false,
savedPosts: false,
};

const CHANNEL_TYPE_ICON: ChannelTypeIcons = {
generalChannels: faMessage,
exerciseChannels: faList,
Expand All @@ -74,6 +64,7 @@ const CHANNEL_TYPE_ICON: ChannelTypeIcons = {
lectureChannels: faFile,
hiddenChannels: faBan,
savedPosts: faBookmark,
recently: faClock,
};

const DEFAULT_COLLAPSE_STATE: CollapseState = {
Expand All @@ -86,6 +77,7 @@ const DEFAULT_COLLAPSE_STATE: CollapseState = {
lectureChannels: true,
hiddenChannels: true,
savedPosts: true,
recently: true,
};

const DEFAULT_SHOW_ALWAYS: SidebarItemShowAlways = {
Expand All @@ -98,6 +90,7 @@ const DEFAULT_SHOW_ALWAYS: SidebarItemShowAlways = {
lectureChannels: false,
hiddenChannels: false,
savedPosts: true,
recently: true,
};

@Component({
Expand Down Expand Up @@ -135,7 +128,6 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
openThreadOnFocus = false;
selectedSavedPostStatus: null | SavedPostStatus = null;

readonly CHANNEL_TYPE_SHOW_ADD_OPTION = CHANNEL_TYPE_SHOW_ADD_OPTION;
readonly CHANNEL_TYPE_ICON = CHANNEL_TYPE_ICON;
readonly DEFAULT_COLLAPSE_STATE = DEFAULT_COLLAPSE_STATE;
protected readonly DEFAULT_SHOW_ALWAYS = DEFAULT_SHOW_ALWAYS;
Expand Down Expand Up @@ -409,8 +401,10 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
prepareSidebarData() {
this.metisConversationService.forceRefresh().subscribe({
complete: () => {
this.sidebarConversations = this.courseOverviewService.mapConversationsToSidebarCardElements(this.conversationsOfUser);
this.accordionConversationGroups = this.courseOverviewService.groupConversationsByChannelType(this.conversationsOfUser, this.messagingEnabled);
this.sidebarConversations = this.courseOverviewService.mapConversationsToSidebarCardElements(this.course!, this.conversationsOfUser);
this.accordionConversationGroups = this.courseOverviewService.groupConversationsByChannelType(this.course!, this.conversationsOfUser, this.messagingEnabled);
const currentConversations = this.sidebarConversations?.filter((item) => item.isCurrent) || [];
this.accordionConversationGroups.recently.entityData = currentConversations;
this.updateSidebarData();
},
});
Expand Down
68 changes: 53 additions & 15 deletions src/main/webapp/app/overview/course-overview.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { isGroupChatDTO } from 'app/entities/metis/conversation/group-chat.model
import { ConversationService } from 'app/shared/metis/conversations/conversation.service';
import { StudentExam } from 'app/entities/student-exam.model';
import { SavedPostStatusMap } from 'app/entities/metis/posting.model';
import { Course } from 'app/entities/course.model';

const DEFAULT_UNIT_GROUPS: AccordionGroups = {
future: { entityData: [] },
Expand Down Expand Up @@ -58,6 +59,7 @@ const GROUP_DECISION_MATRIX: Record<StartDateGroup, Record<EndDateGroup, TimeGro

const DEFAULT_CHANNEL_GROUPS: AccordionGroups = {
favoriteChannels: { entityData: [] },
recently: { entityData: [] },
generalChannels: { entityData: [] },
exerciseChannels: { entityData: [] },
lectureChannels: { entityData: [] },
Expand Down Expand Up @@ -169,20 +171,26 @@ export class CourseOverviewService {
return 'future';
}

getConversationGroup(conversation: ConversationDTO): ChannelGroupCategory {
getConversationGroup(conversation: ConversationDTO): ChannelGroupCategory[] {
const groups: ChannelGroupCategory[] = [];

if (conversation.isFavorite) {
return 'favoriteChannels';
groups.push('favoriteChannels');
}
if (conversation.isHidden) {
return 'hiddenChannels';
groups.push('hiddenChannels');
}

if (isGroupChatDTO(conversation)) {
return 'groupChats';
}
if (isOneToOneChatDTO(conversation)) {
return 'directMessages';
groups.push('groupChats');
} else if (isOneToOneChatDTO(conversation)) {
groups.push('directMessages');
} else {
const subTypeGroup = this.getCorrespondingChannelSubType(getAsChannelDTO(conversation)?.subType);
groups.push(subTypeGroup);
}
return this.getCorrespondingChannelSubType(getAsChannelDTO(conversation)?.subType);

return groups;
}

getCorrespondingChannelSubType(channelSubType: ChannelSubType | undefined): ChannelGroupCategory {
Expand Down Expand Up @@ -219,7 +227,7 @@ export class CourseOverviewService {
return groupedLectureGroups;
}

groupConversationsByChannelType(conversations: ConversationDTO[], messagingEnabled: boolean): AccordionGroups {
groupConversationsByChannelType(course: Course, conversations: ConversationDTO[], messagingEnabled: boolean): AccordionGroups {
const channelGroups = messagingEnabled ? { ...DEFAULT_CHANNEL_GROUPS, groupChats: { entityData: [] }, directMessages: { entityData: [] } } : DEFAULT_CHANNEL_GROUPS;
const groupedConversationGroups = cloneDeep(channelGroups) as AccordionGroups;

Expand Down Expand Up @@ -251,11 +259,21 @@ export class CourseOverviewService {
};

for (const conversation of conversations) {
const conversationGroup = this.getConversationGroup(conversation);
const conversationCardItem = this.mapConversationToSidebarCardElement(conversation);
groupedConversationGroups[conversationGroup].entityData.push(conversationCardItem);
const conversationGroups = this.getConversationGroup(conversation);
const conversationCardItem = this.mapConversationToSidebarCardElement(course, conversation);

for (const group of conversationGroups) {
groupedConversationGroups[group].entityData.push(conversationCardItem);
}
}

for (const group in groupedConversationGroups) {
groupedConversationGroups[group].entityData.sort((a, b) => {
const aIsFavorite = a.conversation?.isFavorite ? 1 : 0;
const bIsFavorite = b.conversation?.isFavorite ? 1 : 0;
return bIsFavorite - aIsFavorite;
});
}
return groupedConversationGroups;
}

Expand All @@ -273,8 +291,8 @@ export class CourseOverviewService {
return exams.map((exam, index) => this.mapExamToSidebarCardElement(exam, studentExams?.[index]));
}

mapConversationsToSidebarCardElements(conversations: ConversationDTO[]) {
return conversations.map((conversation) => this.mapConversationToSidebarCardElement(conversation));
mapConversationsToSidebarCardElements(course: Course, conversations: ConversationDTO[]) {
return conversations.map((conversation) => this.mapConversationToSidebarCardElement(course, conversation));
}

mapLectureToSidebarCardElement(lecture: Lecture): SidebarCardElement {
Expand Down Expand Up @@ -349,14 +367,34 @@ export class CourseOverviewService {
}
}

mapConversationToSidebarCardElement(conversation: ConversationDTO): SidebarCardElement {
mapConversationToSidebarCardElement(course: Course, conversation: ConversationDTO): SidebarCardElement {
let isCurrent = false;
const channelDTO = getAsChannelDTO(conversation);
const subTypeRefId = channelDTO?.subTypeReferenceId;
const now = dayjs();
const oneAndHalfWeekBefore = now.subtract(1.5, 'week');
const oneAndHalfWeekLater = now.add(1.5, 'week');
let dueDate = null;
if (subTypeRefId && course.exercises && channelDTO?.subType === 'exercise') {
const exercise = course.exercises.find((exercise) => exercise.id === subTypeRefId);
dueDate = exercise?.dueDate || null;
} else if (subTypeRefId && course.lectures && channelDTO?.subType === 'lecture') {
const lecture = course.lectures.find((lecture) => lecture.id === subTypeRefId);
dueDate = lecture?.startDate || null;
} else if (subTypeRefId && course.exams && channelDTO?.subType === 'exam') {
const exam = course.exams.find((exam) => exam.id === subTypeRefId);
dueDate = exam?.startDate || null;
}
isCurrent = dueDate ? dayjs(dueDate).isBetween(oneAndHalfWeekBefore, oneAndHalfWeekLater, 'day', '[]') : false;

const conversationCardItem: SidebarCardElement = {
title: this.conversationService.getConversationName(conversation) ?? '',
id: conversation.id ?? '',
type: conversation.type,
icon: this.getChannelIcon(conversation),
conversation: conversation,
size: 'S',
isCurrent: isCurrent,
};
return conversationCardItem;
}
Expand Down
3 changes: 1 addition & 2 deletions src/main/webapp/app/shared/sidebar/sidebar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { faFilter, faFilterCircleXmark, faHashtag, faPeopleGroup, faPlusCircle,
import { ActivatedRoute, Params } from '@angular/router';
import { Subscription, distinctUntilChanged } from 'rxjs';
import { ProfileService } from '../layouts/profiles/profile.service';
import { ChannelAccordionShowAdd, ChannelTypeIcons, CollapseState, SidebarCardSize, SidebarData, SidebarItemShowAlways, SidebarTypes } from 'app/types/sidebar';
import { ChannelTypeIcons, CollapseState, SidebarCardSize, SidebarData, SidebarItemShowAlways, SidebarTypes } from 'app/types/sidebar';
import { SidebarEventService } from './sidebar-event.service';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { cloneDeep } from 'lodash-es';
Expand Down Expand Up @@ -32,7 +32,6 @@ export class SidebarComponent implements OnDestroy, OnChanges, OnInit {
@Input() sidebarData: SidebarData;
@Input() courseId?: number;
@Input() itemSelected?: boolean;
@Input() showAddOption?: ChannelAccordionShowAdd;
@Input() channelTypeIcon?: ChannelTypeIcons;
@Input() collapseState: CollapseState;
sidebarItemAlwaysShow = input.required<SidebarItemShowAlways>();
Expand Down
4 changes: 3 additions & 1 deletion src/main/webapp/app/types/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type AccordionGroups = Record<
>;
export type ChannelGroupCategory =
| 'favoriteChannels'
| 'recently'
| 'generalChannels'
| 'exerciseChannels'
| 'lectureChannels'
Expand All @@ -27,7 +28,6 @@ export type ChannelGroupCategory =
export type CollapseState = {
[key: string]: boolean;
} & (Record<TimeGroupCategory, boolean> | Record<ChannelGroupCategory, boolean> | Record<ExamGroupCategory, boolean> | Record<TutorialGroupCategory, boolean>);
export type ChannelAccordionShowAdd = Record<ChannelGroupCategory, boolean>;
export type ChannelTypeIcons = Record<ChannelGroupCategory, IconProp>;
export type SidebarItemShowAlways = {
[key: string]: boolean;
Expand Down Expand Up @@ -135,4 +135,6 @@ export interface SidebarCardElement {
* Set for Conversation. Will be removed after refactoring
*/
conversation?: ConversationDTO;

isCurrent?: boolean;
}
1 change: 1 addition & 0 deletions src/main/webapp/i18n/de/student-dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"createDirectChat": "Direkt-Chat erstellen",
"groupChats": "Gruppenchats",
"directMessages": "Direktnachrichten",
"recently": "Kürzlich",
"filterConversationPlaceholder": "Konversationen filtern"
},
"menu": {
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/i18n/en/student-dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"createDirectChat": "Create direct chat",
"groupChats": "Group Chats",
"directMessages": "Direct Messages",
"recently": "Recently",
"filterConversationPlaceholder": "Filter conversations"
},
"menu": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ describe('CourseOverviewService', () => {

jest.spyOn(service, 'getCorrespondingChannelSubType');
jest.spyOn(service, 'mapConversationToSidebarCardElement');
const groupedConversations = service.groupConversationsByChannelType(conversations, true);
const groupedConversations = service.groupConversationsByChannelType(course, conversations, true);

expect(groupedConversations['generalChannels'].entityData).toHaveLength(1);
expect(groupedConversations['examChannels'].entityData).toHaveLength(1);
Expand All @@ -445,7 +445,7 @@ describe('CourseOverviewService', () => {

jest.spyOn(service, 'getCorrespondingChannelSubType');
jest.spyOn(service, 'mapConversationToSidebarCardElement');
const groupedConversations = service.groupConversationsByChannelType(conversations, true);
const groupedConversations = service.groupConversationsByChannelType(course, conversations, true);

expect(groupedConversations['generalChannels'].entityData).toHaveLength(2);
expect(service.mapConversationToSidebarCardElement).toHaveBeenCalledTimes(2);
Expand All @@ -460,21 +460,39 @@ describe('CourseOverviewService', () => {
jest.spyOn(service, 'mapConversationToSidebarCardElement');
jest.spyOn(service, 'getConversationGroup');
jest.spyOn(service, 'getCorrespondingChannelSubType');
const groupedConversations = service.groupConversationsByChannelType(conversations, true);
const groupedConversations = service.groupConversationsByChannelType(course, conversations, true);

expect(groupedConversations['generalChannels'].entityData).toHaveLength(2);
expect(groupedConversations['generalChannels'].entityData).toHaveLength(4);
expect(groupedConversations['examChannels'].entityData).toHaveLength(1);
expect(groupedConversations['exerciseChannels'].entityData).toHaveLength(1);
expect(groupedConversations['favoriteChannels'].entityData).toHaveLength(1);
expect(groupedConversations['hiddenChannels'].entityData).toHaveLength(1);
expect(service.mapConversationToSidebarCardElement).toHaveBeenCalledTimes(6);
expect(service.getConversationGroup).toHaveBeenCalledTimes(6);
expect(service.getCorrespondingChannelSubType).toHaveBeenCalledTimes(4);
expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[0].conversation)?.name).toBe('General');
expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[1].conversation)?.name).toBe('General 2');
expect(service.getCorrespondingChannelSubType).toHaveBeenCalledTimes(6);
expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[0].conversation)?.name).toBe('fav-channel');
expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[1].conversation)?.name).toBe('General');
expect(getAsChannelDTO(groupedConversations['generalChannels'].entityData[2].conversation)?.name).toBe('General 2');
expect(getAsChannelDTO(groupedConversations['examChannels'].entityData[0].conversation)?.name).toBe('exam-test');
expect(getAsChannelDTO(groupedConversations['exerciseChannels'].entityData[0].conversation)?.name).toBe('exercise-test');
expect(getAsChannelDTO(groupedConversations['favoriteChannels'].entityData[0].conversation)?.name).toBe('fav-channel');
expect(getAsChannelDTO(groupedConversations['hiddenChannels'].entityData[0].conversation)?.name).toBe('hidden-channel');
});

it('should not remove favorite conversations from their original section but keep them at the top of the related section', () => {
const conversations = [generalChannel, examChannel, exerciseChannel, favoriteChannel];

jest.spyOn(service, 'getCorrespondingChannelSubType');
jest.spyOn(service, 'mapConversationToSidebarCardElement');
jest.spyOn(service, 'getConversationGroup');
const groupedConversations = service.groupConversationsByChannelType(course, conversations, true);

expect(groupedConversations['favoriteChannels'].entityData).toContainEqual(expect.objectContaining({ id: favoriteChannel.id }));

expect(groupedConversations['generalChannels'].entityData[0].id).toBe(favoriteChannel.id);

expect(service.mapConversationToSidebarCardElement).toHaveBeenCalledTimes(4);
expect(service.getConversationGroup).toHaveBeenCalledTimes(4);
expect(service.getCorrespondingChannelSubType).toHaveBeenCalledTimes(4);
});
});
Loading

0 comments on commit 4e30d18

Please sign in to comment.