From c5e41d4967bd070ba3400453410bcbda48628a87 Mon Sep 17 00:00:00 2001 From: mutugiii Date: Mon, 27 Apr 2026 16:21:14 +0300 Subject: [PATCH 1/9] redesign exams/surveys UI/UX --- src/app/exams/exams-view.component.html | 167 ++++++++++++++---------- src/app/exams/exams-view.component.ts | 52 +++++++- src/app/exams/exams-view.scss | 166 ++++++++++++++++------- 3 files changed, 262 insertions(+), 123 deletions(-) diff --git a/src/app/exams/exams-view.component.html b/src/app/exams/exams-view.component.html index 614f854e63..9f95cc4032 100644 --- a/src/app/exams/exams-view.component.html +++ b/src/app/exams/exams-view.component.html @@ -32,86 +32,109 @@ +
+
+
- -
- - - - Enter answer here - - - - - - - - {{option.text}} - - -
- - Other: +
+ +
+ + + + Enter answer here + + + + + + +
+ + {{option.text}} - + check
-
- -
- - {examType, select, survey {You can choose one or more answers.} exam {There are one or more correct answers. Please choose all correct answers.}} - - - {{option.text}} - - -
- - Other: + +
+ + Other: + +
+ +
+ check +
+
+ +
+ + {examType, select, survey {You can choose one or more answers.} exam {There are one or more correct answers. Please choose all correct answers.}} + +
+ + {{option.text}} - + check
- -
-
-
- + +
+ + Other: + +
+ +
+ check +
+
-
+
+
+ +
+
+ Very bad + Very good +
+
+ - - -

Submitted answer:

- - - Correct - Incorrect - - - Comment - - -
- -

Response:

- - -

Grade:

-

Correct

-

Incorrect

+ +

Submitted answer:

+ + + Correct + Incorrect + + + Comment + +
- -

Feedback:

- + +

Response:

+ + +

Grade:

+

Correct

+

Incorrect

+
+ +

Feedback:

+ +
-
+
- + diff --git a/src/app/exams/exams-question.component.html b/src/app/exams/exams-question.component.html index b1842acceb..2f070d552a 100644 --- a/src/app/exams/exams-question.component.html +++ b/src/app/exams/exams-question.component.html @@ -7,9 +7,15 @@ Text - Long answer Multiple Choice - single answer Multiple Choice - multiple answer - Rating Scale - 1 to 9 + Rating Scale +
+ Scale + + {{n}} + +
Question Detail diff --git a/src/app/exams/exams-question.component.ts b/src/app/exams/exams-question.component.ts index b7816029aa..01ac4014e9 100644 --- a/src/app/exams/exams-question.component.ts +++ b/src/app/exams/exams-question.component.ts @@ -16,6 +16,7 @@ import { NgIf, NgFor } from '@angular/common'; import { PlanetMarkdownTextboxComponent } from '../shared/forms/planet-markdown-textbox.component'; import { FormErrorMessagesComponent } from '../shared/forms/form-error-messages.component'; import { MatButton, MatIconButton } from '@angular/material/button'; +import { MatButtonToggle, MatButtonToggleGroup } from '@angular/material/button-toggle'; import { MatCheckbox } from '@angular/material/checkbox'; import { MatInput } from '@angular/material/input'; import { MatIcon } from '@angular/material/icon'; @@ -27,7 +28,7 @@ import { MatIcon } from '@angular/material/icon'; imports: [ FormsModule, ReactiveFormsModule, MatFormField, MatLabel, MatSelect, MatOption, NgIf, PlanetMarkdownTextboxComponent, MatError, FormErrorMessagesComponent, MatButton, NgFor, MatCheckbox, - MatInput, MatIconButton, MatSuffix, MatIcon + MatInput, MatIconButton, MatSuffix, MatIcon, MatButtonToggle, MatButtonToggleGroup ] }) export class ExamsQuestionComponent implements OnInit, OnChanges, OnDestroy, AfterViewChecked { @@ -38,6 +39,7 @@ export class ExamsQuestionComponent implements OnInit, OnChanges, OnDestroy, Aft @Output() questionRemove = new EventEmitter(); @ViewChildren('choiceInput') choiceInputs: QueryList; correctCheckboxes: any = {}; + scaleMaxOptions = [ 5, 6, 7, 8, 9 ]; questionForm: QuestionFormGroup = this.examsService.newQuestionForm(this.examType === 'courses'); initializing = true; choiceAdded = false; diff --git a/src/app/exams/exams-question.scss b/src/app/exams/exams-question.scss index 4b2cfc3238..daadf8ec1d 100644 --- a/src/app/exams/exams-question.scss +++ b/src/app/exams/exams-question.scss @@ -5,6 +5,7 @@ form { display: grid; grid-template-rows: 56px auto 56px; + grid-row-gap: 1rem; } .question-choices { @@ -20,8 +21,21 @@ .type-title-container { display: grid; - grid-template-columns: 1fr v.$form-width-1; - grid-column-gap: 0.5rem; + grid-template-columns: 7fr 3fr; + grid-column-gap: 1rem; + align-items: center; + } + + .scale-max-field { + display: flex; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; + } + + .scale-max-label { + font-size: 14px; + color: rgba(0, 0, 0, 0.6); } } diff --git a/src/app/exams/exams-view.component.html b/src/app/exams/exams-view.component.html index 9f95cc4032..5a998fc4a9 100644 --- a/src/app/exams/exams-view.component.html +++ b/src/app/exams/exams-view.component.html @@ -96,17 +96,20 @@
-
-
-
-
- Very bad - Very good -
-
+ +
+ +
+
+ diff --git a/src/app/exams/exams-view.component.ts b/src/app/exams/exams-view.component.ts index 23604c9bbc..10886867ae 100644 --- a/src/app/exams/exams-view.component.ts +++ b/src/app/exams/exams-view.component.ts @@ -90,6 +90,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { @ViewChild('singleOtherInput') singleOtherInput?: ElementRef; @ViewChild('multipleOtherInput') multipleOtherInput?: ElementRef; progressPercent = 0; + ratingScaleNumbers: number[] = []; private readonly answerValidator: ValidatorFn = (ac: AbstractControl): ValidationErrors | null => { const value = ac.value; if (typeof value === 'string') { @@ -299,6 +300,8 @@ export class ExamsViewComponent implements OnInit, OnDestroy { this.question = questions[this.questionNum - 1]; this.maxQuestions = questions.length; this.progressPercent = this.maxQuestions ? Math.round(((this.questionNum - 1) / this.maxQuestions) * 100) : 0; + const scaleMax = this.question?.scaleMax ?? 9; + this.ratingScaleNumbers = Array.from({ length: scaleMax }, (_, i) => i + 1); this.answer.markAsUntouched(); this.currentOtherOption = { id: 'other', text: '', isOther: true }; } diff --git a/src/app/exams/exams-view.scss b/src/app/exams/exams-view.scss index c2379c08f5..6f13a1d96d 100644 --- a/src/app/exams/exams-view.scss +++ b/src/app/exams/exams-view.scss @@ -120,26 +120,29 @@ .rating-scale-keypad { margin: 8px 0 20px; - } - .rating-scale-row { - display: flex; - gap: 8px; - align-items: center; - flex-wrap: wrap; - max-width: 532px; + &--grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; + max-width: 240px; + } + + &--row { + display: flex; + gap: 8px; + align-items: center; + flex-wrap: wrap; + max-width: 532px; + } } .rating-scale-button { - width: 52px; - height: 52px; - min-width: 52px; padding: 0; border: 2px solid #e0e0e0; border-radius: 10px; background: white; color: #555; - font-size: 18px; font-weight: 500; font-family: inherit; cursor: pointer; @@ -148,26 +151,48 @@ &:hover:not(.selected):not([disabled]) { border-color: v.$primary-light; - background: v.$primary-lighter; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } &.selected { background: v.$primary; color: white; border-color: v.$primary; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.18); } - } - .rating-scale-labels { - display: flex; - justify-content: space-between; - margin-top: 8px; - max-width: 532px; + &--line { + width: 52px; + height: 52px; + min-width: 52px; + font-size: 18px; + + &:hover:not(.selected):not([disabled]) { + background: v.$primary-lighter; + } + } - .rating-scale-label { - font-size: 12px; - color: #9e9e9e; + &--phone { + $rating-bg: ( + 1: #ffeaea, + 2: #ffeeea, + 3: #fff2ea, + 4: #fff6ea, + 5: #fffaea, + 6: #f6faea, + 7: #f2faea, + 8: #eefaea, + 9: #eafaea + ); + + height: 60px; + font-size: 28px; + + @each $rating, $color in $rating-bg { + &[data-rating="#{$rating}"]:not(.selected) { + background-color: $color; + } + } } } diff --git a/src/app/exams/exams.model.ts b/src/app/exams/exams.model.ts index afb081346c..e157fc5ed7 100644 --- a/src/app/exams/exams.model.ts +++ b/src/app/exams/exams.model.ts @@ -17,4 +17,5 @@ export interface ExamQuestion { correctChoice: string[]; marks: number; choices: { text: string, id: string }[]; + scaleMax?: number; } diff --git a/src/app/exams/exams.service.ts b/src/app/exams/exams.service.ts index b24f963b96..5bacb300f5 100644 --- a/src/app/exams/exams.service.ts +++ b/src/app/exams/exams.service.ts @@ -17,6 +17,7 @@ export interface QuestionValue { marks: number; choices: QuestionChoice[]; hasOtherOption: boolean; + scaleMax: number; } export type QuestionChoiceFormGroup = FormGroup<{ @@ -31,6 +32,7 @@ export type QuestionFormGroup = FormGroup<{ marks: FormControl; choices: FormArray; hasOtherOption: FormControl; + scaleMax: FormControl; }>; @Injectable({ @@ -56,7 +58,8 @@ export class ExamsService { choices: this.fb.array( choices.length === 0 ? [] : choices.map(choice => this.newQuestionChoice(choice.id ?? '', choice)) ), - hasOtherOption: this.fb.control(false) + hasOtherOption: this.fb.control(false), + scaleMax: this.fb.control(9) }, { validators: this.choiceRequiredValidator.bind(this) }); return this.setInitalFormValue(formGroup, initialValue); From e4b9c50a9448f7e0e8fce88872f7a3052b8dde50 Mon Sep 17 00:00:00 2001 From: mutugiii Date: Tue, 28 Apr 2026 20:03:18 +0300 Subject: [PATCH 5/9] use current question index for progress --- src/app/exams/exams-view.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/exams/exams-view.component.ts b/src/app/exams/exams-view.component.ts index 10886867ae..c7d21bf98a 100644 --- a/src/app/exams/exams-view.component.ts +++ b/src/app/exams/exams-view.component.ts @@ -299,7 +299,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { setQuestion(questions: any[]) { this.question = questions[this.questionNum - 1]; this.maxQuestions = questions.length; - this.progressPercent = this.maxQuestions ? Math.round(((this.questionNum - 1) / this.maxQuestions) * 100) : 0; + this.progressPercent = this.maxQuestions ? Math.round((this.questionNum / this.maxQuestions) * 100) : 0; const scaleMax = this.question?.scaleMax ?? 9; this.ratingScaleNumbers = Array.from({ length: scaleMax }, (_, i) => i + 1); this.answer.markAsUntouched(); From 36d6bcbf80c087f88f13d12b883186258331a56a Mon Sep 17 00:00:00 2001 From: mutugiii Date: Thu, 7 May 2026 22:28:40 +0300 Subject: [PATCH 6/9] fix scaleMax correctness --- src/app/submissions/submissions.service.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/submissions/submissions.service.ts b/src/app/submissions/submissions.service.ts index b0f22de559..17d55eb898 100644 --- a/src/app/submissions/submissions.service.ts +++ b/src/app/submissions/submissions.service.ts @@ -670,8 +670,9 @@ export class SubmissionsService { } calculateAverageRating(question, submissions): number { + const scaleMax = question.scaleMax ?? 9; const validRatings = submissions.map( - sub => parseInt(sub.answers[question.index].value, 10)).filter(rating => !isNaN(rating) && rating >= 1 && rating <= 9 + sub => parseInt(sub.answers[question.index].value, 10)).filter(rating => !isNaN(rating) && rating >= 1 && rating <= scaleMax ); const sum = validRatings.reduce((total, rating) => total + rating, 0); return parseFloat((sum / validRatings.length).toFixed(1)); @@ -682,9 +683,10 @@ export class SubmissionsService { ) { const totalUsers = submissions.length; const counts: Record> = {}; + const scaleMax = question.scaleMax ?? 9; if (question.type === 'ratingScale') { - for (let i = 1; i <= 9; i++) { + for (let i = 1; i <= scaleMax; i++) { counts[i.toString()] = new Set(); } } else { @@ -723,7 +725,7 @@ export class SubmissionsService { } }); - const labels = question.type === 'ratingScale' ? Array.from({length: 9}, (_, i) => (i + 1).toString()) : Object.keys(counts); + const labels = question.type === 'ratingScale' ? Array.from({length: scaleMax}, (_, i) => (i + 1).toString()) : Object.keys(counts); const userCounts = labels.map(l => counts[l].size); const totalSelections = userCounts.reduce((sum, count) => sum + count, 0); let data: number[]; From f8f49addfb6a224529932ed93d89c99beb70144d Mon Sep 17 00:00:00 2001 From: mutugiii Date: Thu, 7 May 2026 22:29:23 +0300 Subject: [PATCH 7/9] improve scale range selector placement --- src/app/exams/exams-question.component.html | 12 ++++++------ src/app/exams/exams-question.scss | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/exams/exams-question.component.html b/src/app/exams/exams-question.component.html index 2f070d552a..0fc36188b7 100644 --- a/src/app/exams/exams-question.component.html +++ b/src/app/exams/exams-question.component.html @@ -1,5 +1,11 @@
+
+ Scale + + {{n}} + +
Type @@ -10,12 +16,6 @@ Rating Scale -
- Scale - - {{n}} - -
Question Detail diff --git a/src/app/exams/exams-question.scss b/src/app/exams/exams-question.scss index daadf8ec1d..7c3e5fddbc 100644 --- a/src/app/exams/exams-question.scss +++ b/src/app/exams/exams-question.scss @@ -21,7 +21,7 @@ .type-title-container { display: grid; - grid-template-columns: 7fr 3fr; + grid-template-columns: 2fr 8fr; grid-column-gap: 1rem; align-items: center; } From 384acb26ab54a80a9e64ab63badfce154948078c Mon Sep 17 00:00:00 2001 From: mutugiii Date: Thu, 7 May 2026 22:31:58 +0300 Subject: [PATCH 8/9] fix animation and adjust progress bar styling --- src/app/exams/exams-view.component.html | 4 +++- src/app/exams/exams-view.component.ts | 2 ++ src/app/exams/exams-view.scss | 10 +++++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/app/exams/exams-view.component.html b/src/app/exams/exams-view.component.html index 5a998fc4a9..968077aa46 100644 --- a/src/app/exams/exams-view.component.html +++ b/src/app/exams/exams-view.component.html @@ -37,7 +37,9 @@
-
+
diff --git a/src/app/exams/exams-view.component.ts b/src/app/exams/exams-view.component.ts index c7d21bf98a..bb40dfa30e 100644 --- a/src/app/exams/exams-view.component.ts +++ b/src/app/exams/exams-view.component.ts @@ -87,6 +87,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { teamId = this.route.snapshot.params.teamId || null; currentOtherOption: ExamOtherAnswerOption = { id: 'other', text: '', isOther: true }; slideDirection: 'right' | 'left' = 'right'; + slideAnimationVariant: 'a' | 'b' = 'a'; @ViewChild('singleOtherInput') singleOtherInput?: ElementRef; @ViewChild('multipleOtherInput') multipleOtherInput?: ElementRef; progressPercent = 0; @@ -250,6 +251,7 @@ export class ExamsViewComponent implements OnInit, OnDestroy { moveQuestion(direction: number) { if (direction !== 0) { this.slideDirection = direction > 0 ? 'right' : 'left'; + this.slideAnimationVariant = this.slideAnimationVariant === 'a' ? 'b' : 'a'; } if (this.isDialog) { this.questionNum = this.questionNum + direction; diff --git a/src/app/exams/exams-view.scss b/src/app/exams/exams-view.scss index 6f13a1d96d..47eb1d688b 100644 --- a/src/app/exams/exams-view.scss +++ b/src/app/exams/exams-view.scss @@ -29,12 +29,12 @@ } .progress-bar-track { - background: #e0e0e0; + background: #d4deea; } .progress-bar-fill { - background: v.$primary; - height: 4px; + background: #0a2d49; + height: 8px; transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1); } @@ -206,7 +206,7 @@ to { opacity: 1; transform: translateX(0); } } - .slide-in-right { animation: slideInRight 0.28s ease; } - .slide-in-left { animation: slideInLeft 0.28s ease; } + .slide-in-right-a, .slide-in-right-b { animation: slideInRight 0.28s ease; } + .slide-in-left-a, .slide-in-left-b { animation: slideInLeft 0.28s ease; } } From 142d16c9bc2dddd1afffc3a63bcc946a83b75d36 Mon Sep 17 00:00:00 2001 From: dogi Date: Thu, 21 May 2026 16:40:02 -0400 Subject: [PATCH 9/9] Bump version from 0.22.86 to 0.22.90 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 71ed2e6195..85d801ec76 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "planet", "license": "AGPL-3.0", - "version": "0.22.86", + "version": "0.22.90", "myplanet": { "latest": "v0.55.45", "min": "v0.53.91"