diff --git a/src/app/components/creator/content-choice-creator/content-choice-creator.component.ts b/src/app/components/creator/content-choice-creator/content-choice-creator.component.ts index 9856b24dc76b6dc6b67f2eed65efc4b0847fb372..684eafcafc4071e8df2933eafada1914ee4cd9ae 100644 --- a/src/app/components/creator/content-choice-creator/content-choice-creator.component.ts +++ b/src/app/components/creator/content-choice-creator/content-choice-creator.component.ts @@ -63,8 +63,6 @@ export class ContentChoiceCreatorComponent implements OnInit { roomId: string; - lastCollection: string; - constructor(private contentService: ContentService, private notificationService: NotificationService, public dialog: MatDialog, @@ -76,7 +74,6 @@ export class ContentChoiceCreatorComponent implements OnInit { ngOnInit() { this.roomId = localStorage.getItem(`roomId`); this.fillCorrectAnswers(); - this.lastCollection = sessionStorage.getItem('collection'); } fillCorrectAnswers() { @@ -123,14 +120,14 @@ export class ContentChoiceCreatorComponent implements OnInit { return; } } - const points = (this.newAnswerOptionChecked) ? '10' : '-10'; + const points = (this.newAnswerOptionChecked) ? 10 : -10; this.content.options.push(new AnswerOption(this.newAnswerOptionLabel, points)); this.newAnswerOptionChecked = false; this.newAnswerOptionLabel = ''; this.fillCorrectAnswers(); } - openAnswerModificationDialog($event, label: string, points: string, correct: boolean) { + openAnswerModificationDialog($event, label: string, points: number, correct: boolean) { $event.preventDefault(); const index = this.findAnswerIndexByLabel(label); this.editDisplayAnswer = new DisplayAnswer(new AnswerOption(label, points), correct); @@ -149,7 +146,7 @@ export class ContentChoiceCreatorComponent implements OnInit { saveChanges(index: number, answer: DisplayAnswer, matDialogOutput: boolean) { this.content.options[index].label = answer.answerOption.label; - this.content.options[index].points = (answer.correct) ? '10' : '-10'; + this.content.options[index].points = (answer.correct) ? 10 : -10; const indexInCorrectOptionIndexes = this.content.correctOptionIndexes.indexOf(index); if (indexInCorrectOptionIndexes === -1 && answer.correct) { if (this.singleChoice) { @@ -226,6 +223,7 @@ export class ContentChoiceCreatorComponent implements OnInit { } reset($event) { + this.resetP.emit(true); $event.preventDefault(); this.content.subject = ''; this.content.body = ''; @@ -283,14 +281,20 @@ export class ContentChoiceCreatorComponent implements OnInit { this.changesAllowed = true; return; } + let contentGroup: string; + if (this.contentCol === 'Default') { + contentGroup = ''; + } else { + contentGroup = this.contentCol; + } this.contentService.addContent(new ContentChoice( - '', - '', + null, + null, this.roomId, this.contentSub, this.contentBod, 1, - [this.contentCol], + [contentGroup], this.content.options, this.content.correctOptionIndexes, this.content.multiple, diff --git a/src/app/components/creator/content-creator/content-creator.component.ts b/src/app/components/creator/content-creator/content-creator.component.ts index 9b85bdc627f32214e7e0240cdb3b8292a3cdf6b7..0dfa3975bc5e4c67791e78d2a79a72f33976a799 100644 --- a/src/app/components/creator/content-creator/content-creator.component.ts +++ b/src/app/components/creator/content-creator/content-creator.component.ts @@ -27,7 +27,6 @@ export class ContentCreatorComponent implements OnInit { [], ); - lastCollection: string; myControl = new FormControl(); editDialogMode = false; diff --git a/src/app/components/creator/content-likert-creator/content-likert-creator.component.ts b/src/app/components/creator/content-likert-creator/content-likert-creator.component.ts index 6b2ecf1c3b91e1f478fa757d2667b2b58d6895ec..c37fe12e8386c27e6e31d6457754442b4036744c 100644 --- a/src/app/components/creator/content-likert-creator/content-likert-creator.component.ts +++ b/src/app/components/creator/content-likert-creator/content-likert-creator.component.ts @@ -50,7 +50,7 @@ export class ContentLikertCreatorComponent implements OnInit { roomId: string; displayAnswers: DisplayAnswer[] = []; - newAnswerOptionPoints = '0'; + newAnswerOptionPoints = 0; collections: string[] = ['ARSnova', 'Angular', 'HTML', 'TypeScript' ]; myControl = new FormControl(); filteredOptions: Observable<string[]>; @@ -109,14 +109,20 @@ export class ContentLikertCreatorComponent implements OnInit { }); return; } + let contentGroup: string; + if (this.contentCol === 'Default') { + contentGroup = ''; + } else { + contentGroup = this.contentCol; + } this.contentService.addContent(new ContentChoice( - '', - '', + null, + null, this.roomId, this.contentSub, this.contentBod, 1, - [this.contentCol], + [contentGroup], this.content.options, this.content.correctOptionIndexes, this.content.multiple, diff --git a/src/app/components/creator/content-text-creator/content-text-creator.component.ts b/src/app/components/creator/content-text-creator/content-text-creator.component.ts index 37e18da98a7932f0a6835b18eb378fc3b4c9f3b8..fed678afc57e2496929ab9d873a5d86cff7bffee 100644 --- a/src/app/components/creator/content-text-creator/content-text-creator.component.ts +++ b/src/app/components/creator/content-text-creator/content-text-creator.component.ts @@ -51,14 +51,20 @@ export class ContentTextCreatorComponent implements OnInit { } submitContent() { + let contentGroup: string; + if (this.contentCol === 'Default') { + contentGroup = ''; + } else { + contentGroup = this.contentCol; + } this.contentService.addContent(new ContentText( - '1', - '1', + null, + null, this.roomId, this.contentSub, this.contentBod, 1, - [this.contentCol], + [contentGroup], )).subscribe(); if (this.contentSub === '' || this.contentBod === '') { this.translationService.get('content.no-empty').subscribe(message => { diff --git a/src/app/components/creator/content-yes-no-creator/content-yes-no-creator.component.ts b/src/app/components/creator/content-yes-no-creator/content-yes-no-creator.component.ts index cbf5bca7b8316adc29f4f2fdf5bb69e2133bb7b5..4f5f69d2f5953200001813c6e14e2c01b34562ad 100644 --- a/src/app/components/creator/content-yes-no-creator/content-yes-no-creator.component.ts +++ b/src/app/components/creator/content-yes-no-creator/content-yes-no-creator.component.ts @@ -46,7 +46,7 @@ export class ContentYesNoCreatorComponent implements OnInit { roomId: string; displayAnswers: DisplayAnswer[] = []; - newAnswerOptionPoints = ''; + newAnswerOptionPoints = 0; collections: string[] = ['ARSnova', 'Angular', 'HTML', 'TypeScript' ]; myControl = new FormControl(); filteredOptions: Observable<string[]>; @@ -109,14 +109,20 @@ export class ContentYesNoCreatorComponent implements OnInit { } else { this.content.correctOptionIndexes = [1]; } + let contentGroup: string; + if (this.contentCol === 'Default') { + contentGroup = ''; + } else { + contentGroup = this.contentCol; + } this.contentService.addContent(new ContentChoice( - '', - '', + null, + null, this.roomId, this.contentSub, this.contentBod, 1, - [this.contentCol], + [contentGroup], this.content.options, this.content.correctOptionIndexes, this.content.multiple, diff --git a/src/app/components/creator/room-creator-page/room-creator-page.component.scss b/src/app/components/creator/room-creator-page/room-creator-page.component.scss index 81734a3c2b30831c7b3cad584bcb9a601f059c96..cee43eb1022836d4bef30d5d8411474f9019ee02 100644 --- a/src/app/components/creator/room-creator-page/room-creator-page.component.scss +++ b/src/app/components/creator/room-creator-page/room-creator-page.component.scss @@ -9,6 +9,5 @@ mat-card-content > :first-child { button { margin: 5px; - min-width: 30%; - max-width: 30%; + width: 30%; } diff --git a/src/app/components/participant/comment-create-page/comment-create-page.component.ts b/src/app/components/participant/comment-create-page/comment-create-page.component.ts index ef80b582adebf1f03b83d85d6c756c27364512ac..9dafaa1af7882b5a6005f7ea8772ab4ae0188a45 100644 --- a/src/app/components/participant/comment-create-page/comment-create-page.component.ts +++ b/src/app/components/participant/comment-create-page/comment-create-page.component.ts @@ -35,6 +35,8 @@ export class CommentCreatePageComponent implements OnInit { this.roomId = localStorage.getItem(`roomId`); } + // TODO: check if empty + send(subject: string, body: string): void { subject = subject.trim(); body = body.trim(); diff --git a/src/app/components/participant/content-choice-participant/content-choice-participant.component.ts b/src/app/components/participant/content-choice-participant/content-choice-participant.component.ts index c2668f6618f98b1b7088e6150f8fec285613af07..925ef9126be3e14f3a4282dedc16e17de0d122b2 100644 --- a/src/app/components/participant/content-choice-participant/content-choice-participant.component.ts +++ b/src/app/components/participant/content-choice-participant/content-choice-participant.component.ts @@ -50,6 +50,8 @@ export class ContentChoiceParticipantComponent implements OnInit { } } + // TODO: check answers + submitAnswer(): void { let selectedAnswers: number[] = []; if (this.content.multiple) { @@ -61,36 +63,43 @@ export class ContentChoiceParticipantComponent implements OnInit { } else { for (let i = 0; i < this.checkedAnswers.length; i++) { if (this.checkedAnswers[i].answerOption.label === this.selectedSingleAnswer) { - selectedAnswers = [i]; + selectedAnswers.push(i); break; } } } - if (!this.content.multiple && selectedAnswers.length !== 1) { - this.notificationService.show('In single choice mode is only 1 selection allowed'); - this.isAnswerSent = false; - return; - } - if (this.content.multiple && selectedAnswers.length === 0) { - this.notificationService.show('In multiple choice mode is at least 1 selection needed'); + // TODO: i18n + + if (selectedAnswers.length === 0) { + this.notificationService.show('At least 1 selection needed'); this.isAnswerSent = false; return; } this.isAnswerSent = true; this.answerService.addAnswerChoice({ - id: '', - revision: '', + id: null, + revision: null, contentId: this.content.id, round: 1, - selectedChoiceIndexes: [] + selectedChoiceIndexes: selectedAnswers, + creationTimestamp: null, + format: ContentType.CHOICE } as AnswerChoice).subscribe(); - // TODO: Set isAnswerSent + // TODO: replace matchip with notification } abstain($event) { $event.preventDefault(); console.log('abstain'); - // ToDo: Send emtpy answer to backend + this.answerService.addAnswerChoice({ + id: null, + revision: null, + contentId: this.content.id, + round: 1, + selectedChoiceIndexes: [], + creationTimestamp: null, + format: ContentType.CHOICE + } as AnswerChoice).subscribe(); } } diff --git a/src/app/components/participant/content-text-participant/content-text-participant.component.ts b/src/app/components/participant/content-text-participant/content-text-participant.component.ts index 9b808cfde74df0b33ab834747a77160fe77c1d38..4751bb900d223bd4d4ed34d86803de210a6dcba6 100644 --- a/src/app/components/participant/content-text-participant/content-text-participant.component.ts +++ b/src/app/components/participant/content-text-participant/content-text-participant.component.ts @@ -5,6 +5,7 @@ import { AnswerText } from '../../../models/answer-text'; import { NotificationService } from '../../../services/util/notification.service'; import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../services/util/language.service'; +import { ContentType } from '../../../models/content-type.enum'; @Component({ selector: 'app-content-text-participant', @@ -35,16 +36,16 @@ export class ContentTextParticipantComponent implements OnInit { return; } this.isAnswerSent = true; - // ToDo: Check correct api call this.answerService.addAnswerText({ - id: '0', - revision: '0', + id: null, + revision: null, contentId: this.content.id, round: this.content.round, subject: this.content.subject, body: this.textAnswer, read: 'false', - creationTimestamp: new Date() + creationTimestamp: null, + format: ContentType.TEXT } as AnswerText).subscribe(); // TODO: Set isAnswerSent } diff --git a/src/app/components/participant/participant-content-carousel-page/participant-content-carousel-page.component.ts b/src/app/components/participant/participant-content-carousel-page/participant-content-carousel-page.component.ts index 6791be5a4a9fd744bf99643da957cedfa3e7cde4..5959d3cebd7201c9f28e54a5ffa956e3b0ec277b 100644 --- a/src/app/components/participant/participant-content-carousel-page/participant-content-carousel-page.component.ts +++ b/src/app/components/participant/participant-content-carousel-page/participant-content-carousel-page.component.ts @@ -1,20 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { ContentType } from '../../../models/content-type.enum'; import { ContentService } from '../../../services/http/content.service'; -import { ActivatedRoute } from '@angular/router'; import { Content } from '../../../models/content'; - -class ContentGroup { - name: string; - contentIds: string[]; - autoSort: boolean; - - constructor(name: string, contentIds: string[], autoSort: boolean) { - this.name = name; - this.contentIds = contentIds; - this.autoSort = autoSort; - } -} +import { ContentGroup } from '../../../models/content-group'; @Component({ selector: 'app-participant-content-carousel-page', @@ -27,16 +15,13 @@ export class ParticipantContentCarouselPageComponent implements OnInit { contents: Content[]; contentGroup: ContentGroup; - constructor(private contentService: ContentService, - private route: ActivatedRoute) { + constructor(private contentService: ContentService) { } ngOnInit() { - this.route.params.subscribe(params => { this.contentGroup = JSON.parse(sessionStorage.getItem('contentGroup')); this.contentService.getContentsByIds(this.contentGroup.contentIds).subscribe( contents => { this.contents = contents; }); - }); } } diff --git a/src/app/components/shared/content-groups/content-groups.component.ts b/src/app/components/shared/content-groups/content-groups.component.ts index 25f6c9c64fc10e556c91347cf123f44f9fbae85b..ccecffd10127de823222d4af57baa4dafd49104a 100644 --- a/src/app/components/shared/content-groups/content-groups.component.ts +++ b/src/app/components/shared/content-groups/content-groups.component.ts @@ -2,18 +2,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { AuthenticationService } from '../../../services/http/authentication.service'; import { UserRole } from '../../../models/user-roles.enum'; - -class ContentGroup { - name: string; - contentIds: string[]; - autoSort: boolean; - - constructor(name: string, contentIds: string[], autoSort: boolean) { - this.name = name; - this.contentIds = contentIds; - this.autoSort = autoSort; - } -} +import { ContentGroup } from '../../../models/content-group'; @Component({ @@ -24,7 +13,6 @@ class ContentGroup { export class ContentGroupsComponent implements OnInit { @Input() public contentGroups: ContentGroup[]; - displayedContentGroups: ContentGroup[] = []; roomShortId: string; constructor (private route: ActivatedRoute, @@ -35,21 +23,6 @@ export class ContentGroupsComponent implements OnInit { ngOnInit() { this.roomShortId = this.route.snapshot.paramMap.get('roomId'); - Object.keys(this.contentGroups).forEach(key => { - if (key === '') { - const cg = new ContentGroup( - 'Default Content Group', - this.contentGroups[key]['contentIds'], - this.contentGroups[key]['autoSort']); - this.displayedContentGroups.push(cg); - } else { - const cg = new ContentGroup( - key, - this.contentGroups[key]['contentIds'], - this.contentGroups[key]['autoSort']); - this.displayedContentGroups.push(cg); - } - }); } viewContents(contentGroup: ContentGroup) { diff --git a/src/app/components/shared/list-statistic/list-statistic.component.html b/src/app/components/shared/list-statistic/list-statistic.component.html new file mode 100644 index 0000000000000000000000000000000000000000..9be304daa1d8d62045c93fd18c8cd3752d538afc --- /dev/null +++ b/src/app/components/shared/list-statistic/list-statistic.component.html @@ -0,0 +1,41 @@ +<mat-toolbar></mat-toolbar> +<mat-progress-bar class="progress-theme" mode="determinate" value="{{total}}" + *ngIf="total > status.good" color="primary"></mat-progress-bar> +<mat-progress-bar class="progress-theme" mode="determinate" value="{{total}}" + *ngIf="total < status.good && total >= status.okay" color="accent"></mat-progress-bar> +<mat-progress-bar class="progress-theme" mode="determinate" value="{{total}}" + *ngIf="total < status.okay && total > status.empty" color="warn"></mat-progress-bar> +<mat-toolbar></mat-toolbar> +<table mat-table [dataSource]="dataSource" class="mat-elevation-z8"> + + <ng-container matColumnDef="content"> + <mat-header-cell *matHeaderCellDef> {{'content.content' | translate}} </mat-header-cell> + <mat-cell *matCellDef="let cp" [ngClass]="{'positiveC' : cp.percent >= status.good, + 'okayC' : cp.percent >= status.okay && cp.percent < status.good, 'negativeC' : cp.percent < status.okay, + 'emptyCC' : cp.percent < status.zero }">{{cp.content.subject}}</mat-cell> + </ng-container> + + <ng-container matColumnDef="percentage"> + <mat-header-cell *matHeaderCellDef> {{'statistic.percentage' | translate }} </mat-header-cell> + <mat-cell *matCellDef="let cp" [ngClass]="{'positiveC' : cp.percent >= status.good, + 'okayC' : cp.percent >= status.okay && cp.percent < status.good, 'negativeC' : cp.percent < status.okay, + 'emptyC' : cp.percent < status.zero }">{{cp.percent.toFixed() + ' %'}}</mat-cell> + </ng-container> + + <ng-container matColumnDef="counts"> + <mat-header-cell *matHeaderCellDef> {{'statistic.total' | translate}} </mat-header-cell> + <mat-cell *matCellDef="let cp" [ngClass]="{'positiveC' : cp.percent >= status.good, + 'okayC' : cp.percent >= status.okay && cp.percent < status.good, 'negativeC' : cp.percent < status.okay, + 'emptyC' : cp.percent < status.zero }">{{cp.counts}}</mat-cell> + </ng-container> + + <ng-container matColumnDef="abstentions"> + <mat-header-cell *matHeaderCellDef> {{'statistic.abstentions' | translate}} </mat-header-cell> + <mat-cell *matCellDef="let cp" [ngClass]="{'positiveC' : cp.percent >= status.good, + 'okayC' : cp.percent >= status.okay && cp.percent < status.good, 'negativeC' : cp.percent < status.okay, + 'emptyC' : cp.percent < status.zero }">{{cp.abstentions}}</mat-cell> + </ng-container> + + <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> + <mat-row *matRowDef="let row; columns: displayedColumns"></mat-row> +</table> diff --git a/src/app/components/shared/list-statistic/list-statistic.component.scss b/src/app/components/shared/list-statistic/list-statistic.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..991a5539c6bc237f1f9b90c34fafbec45b2b75ae --- /dev/null +++ b/src/app/components/shared/list-statistic/list-statistic.component.scss @@ -0,0 +1,66 @@ +table{ + width: 100%; +} + +.mat-column-percentage { + display: flex; + justify-content: flex-end; +} + +.mat-column-counts { + display: flex; + justify-content: flex-end; +} + +.mat-column-abstentions { + display: flex; + justify-content: flex-end; +} + +mat-header-cell { + color: #3f51b5; + background-color: #e0e0e0; +} + +mat-cell { + background-color: #E0E0E0; +} + +mat-toolbar { + height: 15px; + color: white; +} + +.positiveC { + background-color: #AED581; +} + +.negativeC { + background-color: #FF8A65; +} + +.okayC { + background-color: #FFD54F; +} + +.positiveP { + background-color: #AED581 !important; +} + +.negativeP { + background-color: #FF8A65 !important; +} + +.okayP { + background-color: #FFB74D !important; +} + +.emptyC { + color: #E0E0E0; + background: #E0E0E0; +} + +.emptyCC { + background: #E0E0E0; +} + diff --git a/src/app/components/shared/list-statistic/list-statistic.component.spec.ts b/src/app/components/shared/list-statistic/list-statistic.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..5e765e87bee159987425115a5fbbed517f96d78f --- /dev/null +++ b/src/app/components/shared/list-statistic/list-statistic.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ListStatisticComponent } from './list-statistic.component'; + +describe('ListStatisticComponent', () => { + let component: ListStatisticComponent; + let fixture: ComponentFixture<ListStatisticComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ListStatisticComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ListStatisticComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/shared/list-statistic/list-statistic.component.ts b/src/app/components/shared/list-statistic/list-statistic.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..69a2c2f051f7e26725253f2bf99c14975f569151 --- /dev/null +++ b/src/app/components/shared/list-statistic/list-statistic.component.ts @@ -0,0 +1,157 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ContentGroup } from '../../../models/content-group'; +import { ContentService } from '../../../services/http/content.service'; +import { Content } from '../../../models/content'; +import { ContentType } from '../../../models/content-type.enum'; +import { AnswerOption } from '../../../models/answer-option'; +import { ContentChoice } from '../../../models/content-choice'; +import { Combination } from '../../../models/round-statistics'; +import { TranslateService } from '@ngx-translate/core'; +import { LanguageService } from '../../../services/util/language.service'; + +export class ContentStatistic { + content: Content; + percent: number; + counts: number; + abstentions: number; + + constructor(content: Content, percent: number, counts: number, abstentions: number) { + this.content = content; + this.percent = percent; + this.counts = counts; + this.abstentions = abstentions; + } +} +@Component({ + selector: 'app-list-statistic', + templateUrl: './list-statistic.component.html', + styleUrls: ['./list-statistic.component.scss'] +}) + +export class ListStatisticComponent implements OnInit { + + @Input() contentGroup: ContentGroup; + contents: Content[] = []; + displayedColumns = ['content', 'counts', 'abstentions', 'percentage']; + status = { + good: 85 , + okay: 50 , + empty: -1, + zero: 0 + }; + dataSource: ContentStatistic[]; + total = 0; + totalP = 0; + contentCounter = 0; + + constructor(private contentService: ContentService, + private translateService: TranslateService, + protected langService: LanguageService) { + langService.langEmitter.subscribe(lang => translateService.use(lang)); + } + + ngOnInit() { + this.translateService.use(localStorage.getItem('currentLang')); + this.contentService.getContentChoiceByIds(this.contentGroup.contentIds).subscribe(contents => { + this.getData(contents); + }); + } + + getData(contents: ContentChoice[]) { + this.contents = contents; + const length = contents.length; + let percent; + this.dataSource = new Array<ContentStatistic>(length); + for (let i = 0; i < length; i++) { + this.dataSource[i] = new ContentStatistic(null, 0, 0, 0 ); + this.dataSource[i].content = this.contents[i]; + if (contents[i].format === ContentType.CHOICE) { + this.contentService.getAnswer(contents[i].id).subscribe(answer => { + if (contents[i].multiple) { + percent = this.evaluateMultiple(contents[i].options, answer.roundStatistics[0].combinatedCounts); + } else { + percent = this.evaluateSingle(contents[i].options, answer.roundStatistics[0].independentCounts); + } + this.dataSource[i].abstentions = answer.roundStatistics[0].abstentionCount; + this.dataSource[i].counts = this.getTotalCounts(answer.roundStatistics[0].independentCounts); + this.dataSource[i].percent = percent; + if (percent >= 0) { + this.totalP += percent; + this.total = this.totalP / this.contentCounter; + } else { + this.total = -1; + } + }); + } else { + this.dataSource[i].percent = -1; + } + } + } + + getTotalCounts(indCounts: number[]): number { + let total = 0; + const indLength = indCounts.length; + for (let i = 0; i < indLength; i++) { + total += indCounts[i]; + } + return total; + } + + evaluateSingle(options: AnswerOption[], indCounts: number[]): number { + let correctCounts = 0; + let totalCounts = 0; + const length = options.length; + const correctIndex = new Array<number>(); + let res: number; + for (let i = 0; i < length; i++) { + if (options[i].points > 0) { + correctIndex[0] = i; + } + } + for (let i = 0; i < length; i++) { + if (correctIndex.includes(i)) { + correctCounts += indCounts[i]; + } + totalCounts += indCounts[i]; + } + if (totalCounts) { + res = ((correctCounts / totalCounts) * 100); + this.contentCounter++; + } else { + res = -1; + } + return res; + } + + evaluateMultiple(options: AnswerOption[], combCounts: Combination[]): number { + let combLength; + if (combCounts) { + combLength = combCounts.length; + } else { + return -1; + } + let correctCounts = 0; + let totalCounts = 0; + const optionsLength = options.length; + const correctIndexes = new Array<number>(); + let res: number; + let cic = 0; + for (let i = 0; i < optionsLength; i++) { + if (options[i].points > 0) { + correctIndexes[cic] = i; + cic++; + } + } + for (let i = 0; i < combLength; i++) { + if (combCounts[i].selectedChoiceIndexes.length === correctIndexes.length) { + if (combCounts[i].selectedChoiceIndexes.toString() === correctIndexes.toString()) { + correctCounts += combCounts[i].count; + } + } + totalCounts += combCounts[i].count; + } + res = ((correctCounts / totalCounts) * 100); + this.contentCounter++; + return res; + } +} diff --git a/src/app/components/shared/shared.module.ts b/src/app/components/shared/shared.module.ts index e3af5fe5779c31ab60e5885079ae21b5fc2c59a9..d15a30939213e645d3961a99cb58f51c77f6451f 100644 --- a/src/app/components/shared/shared.module.ts +++ b/src/app/components/shared/shared.module.ts @@ -18,6 +18,7 @@ import { GenericDataDialogComponent } from './_dialogs/generic-data-dialog/gener import { CommentCreatePageComponent } from '../participant/comment-create-page/comment-create-page.component'; import { EssentialsModule } from '../essentials/essentials.module'; import { SharedRoutingModule } from './shared-routing.module'; +import { ListStatisticComponent } from './list-statistic/list-statistic.component'; @NgModule({ imports: [ @@ -42,7 +43,8 @@ import { SharedRoutingModule } from './shared-routing.module'; FeedbackBarometerPageComponent, CommentCreatePageComponent, CommentListComponent, - StatisticsComponent + StatisticsComponent, + ListStatisticComponent ], exports: [ RoomJoinComponent, diff --git a/src/app/components/shared/statistics/statistics.component.html b/src/app/components/shared/statistics/statistics.component.html index a7475655358a1370eb50eb51ea85df171583a567..6bc2ef84ae25f91e008e4d1f6f830511443cbc21 100644 --- a/src/app/components/shared/statistics/statistics.component.html +++ b/src/app/components/shared/statistics/statistics.component.html @@ -1,33 +1,13 @@ <div fxLayout="row" fxLayoutGap="20px" fxLayoutAlign="center"> <mat-card> + <mat-card-header> + <h2>{{'statistic.learning-status' | translate}}</h2> + </mat-card-header> + <mat-divider></mat-divider> <mat-tab-group> - <mat-tab label="Answer statistic"> - <mat-select placeholder="Answers" fxLayoutAlign="right" (change)="showStatistic($event.value)"> - <mat-option *ngFor="let state of states" [value]="state.value"> - {{ state.viewValue }} - </mat-option> - </mat-select> - <div *ngFor="let statistic of statistics"> - {{ statistic.name }} - <mat-progress-bar [value]="statistic.percent"> - </mat-progress-bar> - <div align="right"> Responded answers: {{ statistic.answers }} </div> - </div> - </mat-tab> - <mat-tab label="Evaluation" (choose)="showEvaluation(selectedContent.index)"> - <h2 fxLayoutAlign="center">{{ selectedContent.name }}</h2> - <div class="evaluation" *ngFor="let choice of evaluation"> - {{ choice.name }} - <mat-progress-bar [value]="choice.percent" color="primary" *ngIf="choice.correct"> - </mat-progress-bar> - <mat-progress-bar [value]="choice.percent" color="warn" *ngIf="!choice.correct"> - </mat-progress-bar> - <div align="right"> Selected: {{ choice.answers }} times</div> - </div> - <div fxLayoutAlign="center" fxLayoutGap="10px"> - <button mat-raised-button color="primary" (click)="showEvaluation(selectedContent.index - 1)">Before</button> - <div><b>{{ selectedContent.index }} / {{ selectedContent.length }}</b></div> - <button mat-raised-button color="primary" (click)="showEvaluation(selectedContent.index + 1)">Next</button> + <mat-tab *ngFor="let cg of contentGroups; let i = index" label="{{cg.name}}"> + <div fxLayout="column" fxLayoutAlign="center"> + <app-list-statistic [contentGroup]="cg"></app-list-statistic> </div> </mat-tab> </mat-tab-group> diff --git a/src/app/components/shared/statistics/statistics.component.scss b/src/app/components/shared/statistics/statistics.component.scss index 4483c337510473d8e0dd76cb0c755c742def225c..40cb245ebea5b38d5a5f9976864272ceabfe9227 100644 --- a/src/app/components/shared/statistics/statistics.component.scss +++ b/src/app/components/shared/statistics/statistics.component.scss @@ -2,21 +2,3 @@ mat-card { max-width: 800px; width: 100%; } - -mat-select { - max-width: 150px; - margin-top: 10px; - margin-left: 625px; -} - -mat-progress-bar { - height: 50px; -} - -h1 { - margin-top: 10px; -} - -.evaluation { - margin-top: 10px; -} diff --git a/src/app/components/shared/statistics/statistics.component.ts b/src/app/components/shared/statistics/statistics.component.ts index 30d5109ec04e899967246c2698ff436c96f74222..09f682bb73e5b0e77fc870f4e6fcec02afd9a910 100644 --- a/src/app/components/shared/statistics/statistics.component.ts +++ b/src/app/components/shared/statistics/statistics.component.ts @@ -1,100 +1,39 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { RoomService } from '../../../services/http/room.service'; -import { Content } from '../../../models/content'; -import { ContentService } from '../../../services/http/content.service'; -import { ContentAnswerService } from '../../../services/http/content-answer.service'; -import { AnswerText } from '../../../models/answer-text'; -import { AnswerChoice } from '../../../models/answer-choice'; -import { ContentType } from '../../../models/content-type.enum'; - -/* TODO: Use TranslateService */ +import { ContentGroup } from '../../../models/content-group'; +import { Room } from '../../../models/room'; +import { TranslateService } from '@ngx-translate/core'; +import { LanguageService } from '../../../services/util/language.service'; @Component({ selector: 'app-statistics', templateUrl: './statistics.component.html', styleUrls: ['./statistics.component.scss'] }) + export class StatisticsComponent implements OnInit { - @Input() content: Content[]; - @Input() textAnswers: AnswerText[] = []; - @Input() choiceAnswers: AnswerChoice[] = []; - statistics: any = null; - selectedContent: any = - { name: 'HOW TO MAKE CONTENT GREAT AGAIN', index: '1', length: '1' }; - evaluation: any = [ - { name: 'Skill', percent: 10, correct: false, answers: 1, }, - { name: 'Knowledge', percent: 10, correct: false, answers: 1, }, - { name: '???', percent: 30, correct: true, answers: 3, }, - { name: 'Not at all', percent: 50, correct: true, answers: 5, } - ]; - states = [ - { value: '1', viewValue: 'Text answers' }, - { value: '2', viewValue: 'Choice answers' } - ]; - selected: number = null; + + room: Room; + contentGroups: ContentGroup[]; constructor( private route: ActivatedRoute, private roomService: RoomService, - private contentService: ContentService, - private contentAnswerService: ContentAnswerService ) { } + private translateService: TranslateService, + protected langService: LanguageService) { + langService.langEmitter.subscribe(lang => translateService.use(lang)); + } ngOnInit(): void { - this.route.params.subscribe(params => { - this.getContent(params['roomId']); - }); + this.getRoom(localStorage.getItem('roomId')); } - getContent(roomId: string): void { - this.contentService.getContents(roomId).subscribe(content => { - this.content = content; - this.getAnswers(); + getRoom(id: string): void { + this.translateService.use(localStorage.getItem('currentLang')); + this.roomService.getRoom(id).subscribe(room => { + this.contentGroups = room.contentGroups; }); } - getAnswers(): void { - for (const c of this.content) { - this.contentAnswerService.getAnswers(c.id).subscribe( answer => { - [].push.apply(this.textAnswers, answer); - }); - this.contentAnswerService.getAnswers(c.id).subscribe( answer => { - [].push.apply(this.choiceAnswers, answer); - }); - } - } - - showStatistic(value) { // refactor answer class structure for less code and more abstraction - this.statistics = []; - for (const c of this.content) { - if (value === '1') { - if (c.format === ContentType.TEXT) { - const count = this.countTextAnswers(c.id); - this.statistics.push({ - name: c.subject, answers: count, percent: count * 100 / this.textAnswers.length, - }); - } - } else { - if (c.format === ContentType.CHOICE) { - const count = this.countChoiceAnswers(c.id); - this.statistics.push({ - name: c.subject, answers: count, percent: count * 100 / this.choiceAnswers.length, - }); - } - } - } - this.selected = value; - } - - countTextAnswers(contentId: string): number { - return this.textAnswers.filter(answer => answer.contentId === contentId).length; - } - - countChoiceAnswers(contentId: string): number { - return this.choiceAnswers.filter(answer => answer.contentId === contentId).length; - } - - showEvaluation(index: number) { - // coming with api connection, logic doesnt make sense without knowledge about api - } } diff --git a/src/app/models/answer-choice.ts b/src/app/models/answer-choice.ts index fd5e7ea04d8302701b5dc421a4fca563d540fc09..9c3bf8e4484219da25c1816efbec7f9e51353c47 100644 --- a/src/app/models/answer-choice.ts +++ b/src/app/models/answer-choice.ts @@ -1,7 +1,11 @@ +import { ContentType } from './content-type.enum'; + export class AnswerChoice { id: string; revision: string; contentId: string; round: number; selectedChoiceIndexes: number[]; + creationTimestamp: Date; + format: ContentType; } diff --git a/src/app/models/answer-option.ts b/src/app/models/answer-option.ts index 576982bce9c8b7cc5c443a01aeb53a03f8b3f424..50aa4d1228b0de8c0de36dceb6013196cd0ce39e 100644 --- a/src/app/models/answer-option.ts +++ b/src/app/models/answer-option.ts @@ -1,8 +1,8 @@ export class AnswerOption { label: string; - points: string; + points: number; - constructor(label: string, points: string) { + constructor(label: string, points: number) { this.label = label; this.points = points; } diff --git a/src/app/models/answer-statistics.ts b/src/app/models/answer-statistics.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e3d0311de296cb69ee15075f0accfe4dc657ad7 --- /dev/null +++ b/src/app/models/answer-statistics.ts @@ -0,0 +1,6 @@ +import { RoundStatistics } from './round-statistics'; + +export class AnswerStatistics { + contentId: string; + roundStatistics: RoundStatistics[]; +} diff --git a/src/app/models/answer-text.ts b/src/app/models/answer-text.ts index b2014e2d8dcf7bcc12a50cdb48563dcef2766ff9..af8e6d09796a766aabf5238f95c9a2bd84b2ded9 100644 --- a/src/app/models/answer-text.ts +++ b/src/app/models/answer-text.ts @@ -1,3 +1,5 @@ +import { ContentType } from './content-type.enum'; + export class AnswerText { id: string; revision: string; @@ -7,4 +9,5 @@ export class AnswerText { body: string; read: string; creationTimestamp: Date; + format: ContentType; } diff --git a/src/app/models/round-statistics.ts b/src/app/models/round-statistics.ts new file mode 100644 index 0000000000000000000000000000000000000000..e88befa141685ba9429360fca51f0d41e5127d80 --- /dev/null +++ b/src/app/models/round-statistics.ts @@ -0,0 +1,13 @@ +export class Combination { + count: number; + selectedChoiceIndexes: number[]; +} + + + +export class RoundStatistics { + round: number; + independentCounts: number[]; + combinatedCounts: Combination[]; + abstentionCount: number; +} diff --git a/src/app/services/http/content-answer.service.ts b/src/app/services/http/content-answer.service.ts index b40af7cfc86d52b2a31be551d8bfef46cbc20728..389f9a9161ebe513128a656e2b9dac7ce32fa84a 100644 --- a/src/app/services/http/content-answer.service.ts +++ b/src/app/services/http/content-answer.service.ts @@ -35,14 +35,14 @@ export class ContentAnswerService extends BaseHttpService { } addAnswerText(answerText: AnswerText): Observable<AnswerText> { - const url = this.apiUrl.base + this.apiUrl.answer + this.apiUrl.text + '/'; + const url = this.apiUrl.base + this.apiUrl.answer + '/'; return this.http.post<AnswerText>(url, answerText, httpOptions).pipe( catchError(this.handleError<AnswerText>('addAnswerText')) ); } addAnswerChoice(answerChoice: AnswerChoice): Observable<AnswerChoice> { - const url = this.apiUrl.base + this.apiUrl.answer + this.apiUrl.choice + '/'; + const url = this.apiUrl.base + this.apiUrl.answer + '/'; return this.http.post<AnswerChoice>(url, answerChoice, httpOptions).pipe( catchError(this.handleError<AnswerChoice>('addAnswerChoice')) ); diff --git a/src/app/services/http/content.service.ts b/src/app/services/http/content.service.ts index ef45984afcf95cc9e2fb866e42f22be27b1b869b..d2fc8c5335e033c70d9967ac1bffe50706da347d 100644 --- a/src/app/services/http/content.service.ts +++ b/src/app/services/http/content.service.ts @@ -4,8 +4,9 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; import { BaseHttpService } from './base-http.service'; +import { AnswerChoice } from '../../models/answer-choice'; +import { AnswerStatistics } from '../../models/answer-statistics'; import { ContentChoice } from '../../models/content-choice'; -import { ContentGroup } from '../../models/content-group'; const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) @@ -41,9 +42,15 @@ export class ContentService extends BaseHttpService { ); } + getContentChoiceByIds(ids: string[]): Observable<ContentChoice[]> { + const connectionUrl = this.apiUrl.base + this.apiUrl.content + '/?ids=' + ids; + return this.http.get<ContentChoice[]>(connectionUrl).pipe( + tap(() => ''), + catchError(this.handleError('getContentsByIds', [])) + ); + } + addContent(content: Content): Observable<Content> { - delete content.id; - delete content.revision; const connectionUrl = this.apiUrl.base + this.apiUrl.content + '/'; return this.http.post<Content>(connectionUrl, content, @@ -52,16 +59,6 @@ export class ContentService extends BaseHttpService { ); } - addContentChoice(contentChoice: ContentChoice): Observable<ContentChoice> { - const connectionUrl = this.apiUrl.base + this.apiUrl.content + '/'; - return this.http.post<ContentChoice>(connectionUrl, - { roomId: contentChoice.roomId, subject: contentChoice.subject, body: contentChoice.body, - format: contentChoice.format, group: 'preparation' }, - httpOptions).pipe( - catchError(this.handleError<ContentChoice>('addContent')) - ); - } - updateContent(updatedContent: Content): Observable<Content> { const connectionUrl = this.apiUrl.base + this.apiUrl.content + '/' + updatedContent.id; return this.http.put(connectionUrl, updatedContent, httpOptions).pipe( @@ -77,4 +74,12 @@ export class ContentService extends BaseHttpService { catchError(this.handleError<Content>('deleteContent')) ); } + + getAnswer(contentId: string): Observable<AnswerStatistics> { + const connectionUrl = this.apiUrl.base + this.apiUrl.content + '/' + contentId + '/stats'; + return this.http.get<AnswerStatistics>(connectionUrl).pipe( + tap(() => ''), + catchError(this.handleError<AnswerStatistics>(`getRoom shortId=${ contentId }`)) + ); + } } diff --git a/src/app/services/http/user.service.ts b/src/app/services/http/user.service.ts index ceb850b4be08f7a280893e5379f7b927581e5895..a05b033d847d6b3986d8dadd5c3d6a3311f5dfb6 100644 --- a/src/app/services/http/user.service.ts +++ b/src/app/services/http/user.service.ts @@ -1,8 +1,6 @@ import { Injectable } from '@angular/core'; -import { User } from '../../models/user'; -import { Observable , of } from 'rxjs'; +import { Observable } from 'rxjs/Observable'; import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { ClientAuthentication } from '../../models/client-authentication'; import { BaseHttpService } from './base-http.service'; const httpOptions = { diff --git a/src/assets/i18n/creator/de.json b/src/assets/i18n/creator/de.json index b5645bf47ce8d86881ff765d98836164ecf294bb..6d3d17fedca05ae23f28639bd0e31e340082d868 100644 --- a/src/assets/i18n/creator/de.json +++ b/src/assets/i18n/creator/de.json @@ -21,6 +21,7 @@ "default-content-group": "Standard" }, "content": { + "content": "Frage", "create": "Erstellen", "collection": "Sammlung", "body": "Inhalt", @@ -54,5 +55,11 @@ "description": "Beschreibung", "max-ls": "Max. Zeichen:", "create-session": "Session erstellen" + }, + "statistic": { + "learning-status": "Lernstand", + "total": "Total", + "percentage": "Prozent", + "abstentions": "Enthaltungen" } } diff --git a/src/assets/i18n/creator/en.json b/src/assets/i18n/creator/en.json index 2da377443761a625568b626002c483308c28c6e5..95f47fa0a7288f901f4a50a395277455d5732ac1 100644 --- a/src/assets/i18n/creator/en.json +++ b/src/assets/i18n/creator/en.json @@ -20,6 +20,7 @@ "default-content-group": "Default" }, "content": { + "content": "Content", "create": "Create", "collection": "Collection", "body": "Body", @@ -53,5 +54,11 @@ "description": "Description", "max-ls": "Max. letters / signs:", "create-session": "Create session" + }, + "statistic": { + "learning-status": "Learning status", + "total": "Total", + "percentage": "Percentage", + "abstentions": "Abstentions" } } diff --git a/src/assets/i18n/participant/de.json b/src/assets/i18n/participant/de.json index 2d4131fd14390116ff155b794f40d5e5f50a5d8d..ddd018813ed9c2d4fe417abc701c134d817a8e4c 100644 --- a/src/assets/i18n/participant/de.json +++ b/src/assets/i18n/participant/de.json @@ -25,5 +25,11 @@ "abstain": "Enthalten", "sent": "Antwort gesendet.", "your-answer": "Ihre Antwort" + }, + "statistic": { + "learning-status": "Lernstand", + "total": "Total", + "percentage": "Prozent", + "abstentions": "Enthaltungen" } } diff --git a/src/assets/i18n/participant/en.json b/src/assets/i18n/participant/en.json index 039bc8b02db68ec1dc98797c6bfb77eeb19cee21..a7b7f403f35e9cf76146b1577a63f380f258e3c6 100644 --- a/src/assets/i18n/participant/en.json +++ b/src/assets/i18n/participant/en.json @@ -25,5 +25,11 @@ "abstain": "Abstain", "sent": "Answer sent.", "your-answer": "Your answer" + }, + "statistic": { + "learning-status": "Learning status", + "total": "Total", + "percentage": "Percentage", + "abstentions": "Abstentions" } } diff --git a/src/theme/_theme.scss b/src/theme/_theme.scss index b960620909c641d4e2abb914cdb484ef92b07148..a13ef7ac2dd270b6882f904fe2be6102b6f6ad46 100644 --- a/src/theme/_theme.scss +++ b/src/theme/_theme.scss @@ -1,3 +1,5 @@ +@import '~@angular/material/theming'; + html, body { font-family: 'Roboto', 'Helvetica Neue', sans-serif; margin: 0; @@ -8,3 +10,13 @@ html, body { @import '_util.scss'; @import '_form.scss'; + +.progress-theme { + $progress-primary: mat-palette($mat-light-green, 300); + $progress-accent: mat-palette($mat-amber, 300); + $progress-warn: mat-palette($mat-deep-orange, 300); + + $progress-theme: mat-light-theme($progress-primary, $progress-accent, $progress-warn); + + @include angular-material-theme($progress-theme); +}