From b783f727b3cb025255a7463ef58c4f94b1eae333 Mon Sep 17 00:00:00 2001 From: trtshen Date: Wed, 17 Sep 2025 10:46:15 +0800 Subject: [PATCH 1/7] [CORE-7944] broken multi member selctor fixed --- package-lock.json | 44 ------------------- .../assessment/assessment.component.ts | 8 +++- .../multi-team-member-selector.component.html | 4 +- .../multi-team-member-selector.component.ts | 12 +++-- .../v3/src/app/components/types/assessment.ts | 6 +++ .../v3/src/app/services/assessment.service.ts | 2 +- 6 files changed, 25 insertions(+), 51 deletions(-) diff --git a/package-lock.json b/package-lock.json index 53b41707a..6edbcdd08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -704,17 +704,6 @@ "node": ">=12" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@types/node": { - "version": "22.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", - "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~6.19.8" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/@vitejs/plugin-basic-ssl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", @@ -4371,31 +4360,6 @@ } } }, - "node_modules/@compodoc/compodoc/node_modules/@angular-devkit/schematics/node_modules/chokidar": { - "version": "3.6.0", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/@compodoc/compodoc/node_modules/@babel/core": { "version": "7.25.8", "dev": true, @@ -23939,14 +23903,6 @@ "through": "^2.3.8" } }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/uni-global": { "version": "1.0.0", "dev": true, diff --git a/projects/v3/src/app/components/assessment/assessment.component.ts b/projects/v3/src/app/components/assessment/assessment.component.ts index 2ccaeaf91..e6c424b51 100644 --- a/projects/v3/src/app/components/assessment/assessment.component.ts +++ b/projects/v3/src/app/components/assessment/assessment.component.ts @@ -514,8 +514,14 @@ Best regards`; } } + // multiple initial answer + let answer: string | object | any[] = ''; + if (question.type === 'multi team member selector') { + answer = []; + } + this.questionsForm.addControl('q-' + question.id, new FormControl({ - answer: '', + answer, comment: '', file: null, }, validator)); diff --git a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html index c7c6160d3..bc537d618 100644 --- a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html +++ b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html @@ -29,15 +29,17 @@

+ + ; - @Input() question; + @Input() question: Question; @Input() submission; @Input() submissionId: number; @Input() review; @@ -107,9 +108,12 @@ export class MultiTeamMemberSelectorComponent implements ControlValueAccessor, O } } else { if (!this.innerValue) { - this.innerValue = []; + this.innerValue = { + answer: [], + comment: '' + }; } - this.innerValue = this.utils.addOrRemove(this.innerValue, value); + this.innerValue.answer = this.utils.addOrRemove(this.innerValue.answer, value); } // propagate value into form control using control value accessor interface @@ -150,7 +154,7 @@ export class MultiTeamMemberSelectorComponent implements ControlValueAccessor, O private _showSavedAnswers() { if ((['in progress', 'not start'].includes(this.reviewStatus)) && this.doReview) { this.innerValue = { - answer: this.review.answer, + answer: this.review.answer || [], comment: this.review.comment }; this.comment = this.review.comment; diff --git a/projects/v3/src/app/components/types/assessment.ts b/projects/v3/src/app/components/types/assessment.ts index 4352fb2a6..fa97570ff 100644 --- a/projects/v3/src/app/components/types/assessment.ts +++ b/projects/v3/src/app/components/types/assessment.ts @@ -45,6 +45,12 @@ export interface Choice { explanation?: string | any; } +export interface TeamMemberKey { + userId: number; + userName: string; + teamId: number; +} + export interface TeamMember { key: string; userName: string; diff --git a/projects/v3/src/app/services/assessment.service.ts b/projects/v3/src/app/services/assessment.service.ts index b1bbf6c2c..f830811ba 100644 --- a/projects/v3/src/app/services/assessment.service.ts +++ b/projects/v3/src/app/services/assessment.service.ts @@ -546,7 +546,7 @@ export class AssessmentService { if (this.utils.isEmpty(answer)) { answer = []; } - if (!Array.isArray(answer)) { + if (!Array.isArray(answer) && typeof answer === "string" && answer.length > 0) { // re-format json string to array answer = JSON.parse(answer); } From db22405e0f0d8d974db1c1d57a4a2c242a04c1de Mon Sep 17 00:00:00 2001 From: trtshen Date: Wed, 17 Sep 2025 15:41:46 +0800 Subject: [PATCH 2/7] [CORE-7944] broken logic for multi answer retrieval + submission --- .../assessment/assessment.component.ts | 2 +- .../multi-team-member-selector.component.html | 22 +++--- .../multi-team-member-selector.component.ts | 69 ++++++++++++++++--- .../multiple/multiple.component.html | 8 ++- .../components/multiple/multiple.component.ts | 3 - .../app/components/oneof/oneof.component.html | 7 +- 6 files changed, 87 insertions(+), 24 deletions(-) diff --git a/projects/v3/src/app/components/assessment/assessment.component.ts b/projects/v3/src/app/components/assessment/assessment.component.ts index e6c424b51..c01215fb9 100644 --- a/projects/v3/src/app/components/assessment/assessment.component.ts +++ b/projects/v3/src/app/components/assessment/assessment.component.ts @@ -585,7 +585,7 @@ Best regards`; } private _handleReviewData() { - if (this.isPendingReview && this.review.status === 'in progress') { + if (this.isPendingReview && this.review?.status === 'in progress') { this.savingMessage$.next($localize`Last saved ${this.utils.timeFormatter(this.review.modified)}`); this.btnDisabled$.next(false); } diff --git a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html index bc537d618..a56208c4a 100644 --- a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html +++ b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html @@ -5,10 +5,17 @@

-

- Learner's answer - Expert's answer -

+ +

+ Learner's answer +

+
+ + +

+ Expert's answer +

+
@@ -29,7 +36,6 @@

-
-

+

Learner's answer

diff --git a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts index 577ebc6ba..b67027e0c 100644 --- a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts +++ b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts @@ -33,7 +33,7 @@ export class MultiTeamMemberSelectorComponent implements ControlValueAccessor, O // this is for doing review or not @Input() doReview: Boolean; // FormControl that is passed in from parent component - @Input() control: AbstractControl; + @Input() control: AbstractControl<{answer: string[], comment: string}>; // answer field for submitter & reviewer @ViewChild('answerEle') answerRef: ElementRef; // comment field for reviewer @@ -80,7 +80,7 @@ export class MultiTeamMemberSelectorComponent implements ControlValueAccessor, O action.questionSave = { submissionId: this.submissionId, questionId: this.question.id, - answer: this.innerValue, + answer: this.innerValue.answer, }; } @@ -113,6 +113,7 @@ export class MultiTeamMemberSelectorComponent implements ControlValueAccessor, O comment: '' }; } + this.innerValue.answer = this.utils.addOrRemove(this.innerValue.answer, value); } @@ -135,9 +136,6 @@ export class MultiTeamMemberSelectorComponent implements ControlValueAccessor, O // From ControlValueAccessor interface writeValue(value: any) { - if (value) { - this.innerValue = typeof value === 'string' ? JSON.parse(value) : value; - } } // From ControlValueAccessor interface @@ -158,9 +156,13 @@ export class MultiTeamMemberSelectorComponent implements ControlValueAccessor, O comment: this.review.comment }; this.comment = this.review.comment; - } - if ((this.submissionStatus === 'in progress') && this.doAssessment) { - this.innerValue = this.control.pristine ? this.submission.answer : this.control.value; + } else if ((this.submissionStatus === 'in progress') && this.doAssessment) { + if (!this.innerValue) { + this.innerValue = { + answer: this.submission.answer || [], + }; + } + this.innerValue.answer = this.control.pristine ? this.submission.answer : this.control.value; } this.propagateChange(this.innerValue); @@ -181,4 +183,55 @@ export class MultiTeamMemberSelectorComponent implements ControlValueAccessor, O return !this.doAssessment && !this.doReview && (this.submissionStatus === 'feedback available' || this.submissionStatus === 'pending review' || (this.submissionStatus === 'done' && this.reviewStatus === '')) && (this.submission?.answer || this.review?.answer); } + + isSelected(teamMember: any): boolean { + if (!this.innerValue?.answer) return false; + try { + const memberObj = JSON.parse(teamMember.key); + return this.innerValue.answer.some((ans: string) => { + try { + const ansObj = JSON.parse(ans); + return ansObj.userId === memberObj.userId; + } catch { + return false; + } + }); + } catch { + return false; + } + } + + isSelectedInSubmission(teamMember: any): boolean { + if (!this.submission?.answer) return false; + try { + const memberObj = JSON.parse(teamMember.key); + return this.submission.answer.some((ans: string) => { + try { + const ansObj = JSON.parse(ans); + return ansObj.userId === memberObj.userId; + } catch { + return false; + } + }); + } catch { + return false; + } + } + + isSelectedInReview(teamMember: any): boolean { + if (!this.review?.answer) return false; + try { + const memberObj = JSON.parse(teamMember.key); + return this.review.answer.some((ans: string) => { + try { + const ansObj = JSON.parse(ans); + return ansObj.userId === memberObj.userId; + } catch { + return false; + } + }); + } catch { + return false; + } + } } diff --git a/projects/v3/src/app/components/multiple/multiple.component.html b/projects/v3/src/app/components/multiple/multiple.component.html index 87f1728c3..8e1e4b256 100644 --- a/projects/v3/src/app/components/multiple/multiple.component.html +++ b/projects/v3/src/app/components/multiple/multiple.component.html @@ -44,15 +44,19 @@

{ - + + > + + {{question. [ngClass]="{'item-bottom-border': i !== question.choices.length - 1 || !checkInnerValue(choice.id)}"> + > + From 29f0d644071fa350606eef3aefc3d26c849596e2 Mon Sep 17 00:00:00 2001 From: trtshen Date: Thu, 18 Sep 2025 12:44:43 +0800 Subject: [PATCH 3/7] [CORE-8012] toggle with text innerHTML --- docs/directives/toggleLabelDirective.md | 37 +++++++++ docs/docs.md | 5 +- .../src/app/components/components.module.ts | 3 + .../multi-team-member-selector.component.html | 17 +++- .../multi-team-member-selector.component.ts | 9 +++ .../multiple/multiple.component.html | 17 +++- .../components/multiple/multiple.component.ts | 5 ++ .../app/components/oneof/oneof.component.html | 17 +++- .../app/components/oneof/oneof.component.ts | 10 +++ .../team-member-selector.component.html | 17 +++- .../team-member-selector.component.ts | 10 +++ .../toggle-label.directive.spec.ts | 81 +++++++++++++++++++ .../toggle-label/toggle-label.directive.ts | 31 +++++++ 13 files changed, 250 insertions(+), 9 deletions(-) create mode 100644 docs/directives/toggleLabelDirective.md create mode 100644 projects/v3/src/app/directives/toggle-label/toggle-label.directive.spec.ts create mode 100644 projects/v3/src/app/directives/toggle-label/toggle-label.directive.ts diff --git a/docs/directives/toggleLabelDirective.md b/docs/directives/toggleLabelDirective.md new file mode 100644 index 000000000..4e323ec48 --- /dev/null +++ b/docs/directives/toggleLabelDirective.md @@ -0,0 +1,37 @@ +# Toggle Label Directive + +This directive was created to accommodate the use of innerHTML in ion-checkbox and ion-radio labels. It provides a solution for dynamically toggling label content and handling HTML content within Ionic form controls where standard label binding may not be sufficient. + +## Problem Statement +The issue arises when clicking on the content within the innerHTML title text of ion-checkbox or ion-radio components. The expected behavior is that clicking the label should toggle the checkbox or radio button state. However, due to the way innerHTML is handled, the click event does not propagate correctly to the underlying input element, preventing the toggle action from occurring. This can lead to a confusing user experience where the label appears clickable but does not perform the intended function. + +### Comparison of Implementations + +#### Default Recommended Code Implementation +```html + + Toggle me + +``` +In this implementation, clicking the label correctly toggles the checkbox state because the label is directly associated with the checkbox input. + +#### Implementation with innerHTML +```html + + + +``` +In this case, while the label appears clickable, the click event does not propagate to the checkbox input, resulting in no toggle action occurring when the label is clicked. + +This comparison highlights the importance of using standard label binding to ensure proper functionality in Ionic form controls. + +## Purpose +- Enable dynamic label content for Ionic checkbox and radio components +- Support HTML content rendering within form control labels +- Provide consistent label behavior across different form input types + +## Usage +Apply this directive to elements that need dynamic label toggling functionality, particularly useful with Ionic form controls that require innerHTML support. + +## Note +This directive addresses limitations in standard Ionic label handling where innerHTML content needs to be dynamically managed for checkbox and radio controls. diff --git a/docs/docs.md b/docs/docs.md index 598cc275f..585bbe02d 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -7,4 +7,7 @@ This is practera documentation with more informations. ### Components - [Chat Room Component](./components/chatRoomComponent.md) -- [Chat List Component](./components/chatListComponent.md) \ No newline at end of file +- [Chat List Component](./components/chatListComponent.md) + +### Directives +- [Toggle Label Directive](./directives/toggleLabelDirective.md) \ No newline at end of file diff --git a/projects/v3/src/app/components/components.module.ts b/projects/v3/src/app/components/components.module.ts index f48f599f1..cddb662b9 100644 --- a/projects/v3/src/app/components/components.module.ts +++ b/projects/v3/src/app/components/components.module.ts @@ -38,6 +38,7 @@ import { VideoConversionComponent } from './video-conversion/video-conversion.co import { SupportPopupComponent } from './support-popup/support-popup.component'; import { BackgroundImageDirective } from '../directives/background-image/background-image.directive'; import { FallbackImageDirective } from '../directives/fallback-image/fallback-image.directive'; +import { ToggleLabelDirective } from '../directives/toggle-label/toggle-label.directive'; import { TrafficLightGroupComponent } from './traffic-light-group/traffic-light-group.component'; import { UppyUploaderComponent } from './uppy-uploader/uppy-uploader.component'; import { FileUploadComponent } from './file-upload/file-upload.component'; @@ -67,6 +68,7 @@ const largeCircleDefaultConfig = { CommonModule, FormsModule, ReactiveFormsModule, + ToggleLabelDirective, UppyAngularDashboardModalModule, UppyAngularDashboardModule, NgCircleProgressModule.forRoot(largeCircleDefaultConfig), @@ -152,6 +154,7 @@ const largeCircleDefaultConfig = { BrandingLogoComponent, BottomActionBarComponent, SupportPopupComponent, + ToggleLabelDirective, TrafficLightComponent, TrafficLightGroupComponent, UppyUploaderComponent, diff --git a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html index a56208c4a..c77c7936f 100644 --- a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html +++ b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html @@ -40,7 +40,14 @@

- + +
- + +

Learner's answer

diff --git a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts index b67027e0c..07ac2595c 100644 --- a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts +++ b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts @@ -234,4 +234,13 @@ export class MultiTeamMemberSelectorComponent implements ControlValueAccessor, O return false; } } + + // innerHTML toggle label click handler + onLabelToggle = (id: string): void => { + this.onChange(id); + } + + onLabelToggleReview = (id: string): void => { + this.onChange(id, 'answer'); + } } diff --git a/projects/v3/src/app/components/multiple/multiple.component.html b/projects/v3/src/app/components/multiple/multiple.component.html index 8e1e4b256..1782e8661 100644 --- a/projects/v3/src/app/components/multiple/multiple.component.html +++ b/projects/v3/src/app/components/multiple/multiple.component.html @@ -55,7 +55,14 @@

{ [disabled]="control.disabled" [attr.aria-label]="choice.name" > - + + @@ -93,7 +100,13 @@

{ [attr.aria-label]="choice.name" >
- + +

{ + this.onChange(id); + } } diff --git a/projects/v3/src/app/components/oneof/oneof.component.html b/projects/v3/src/app/components/oneof/oneof.component.html index f22fc802d..b1475f18c 100644 --- a/projects/v3/src/app/components/oneof/oneof.component.html +++ b/projects/v3/src/app/components/oneof/oneof.component.html @@ -47,7 +47,14 @@

{{question. - + + {{question. justify="start" labelPlacement="end">
- + +

Learner's answer

diff --git a/projects/v3/src/app/components/oneof/oneof.component.ts b/projects/v3/src/app/components/oneof/oneof.component.ts index 03c847e27..223621f1b 100644 --- a/projects/v3/src/app/components/oneof/oneof.component.ts +++ b/projects/v3/src/app/components/oneof/oneof.component.ts @@ -195,4 +195,14 @@ export class OneofComponent implements AfterViewInit, ControlValueAccessor, OnIn return !this.doAssessment && !this.doReview && (this.submissionStatus === 'feedback available' || this.submissionStatus === 'pending review' || (this.submissionStatus === 'done' && this.reviewStatus === '')); } + + // innerHTML text toggle + onLabelToggle = (id: string): void => { + this.onChange(id); + } + + // Allow clicking the rendered HTML label to toggle during review + onLabelToggleReview = (id: string): void => { + this.onChange(id, 'answer'); + } } diff --git a/projects/v3/src/app/components/team-member-selector/team-member-selector.component.html b/projects/v3/src/app/components/team-member-selector/team-member-selector.component.html index 02cf7563e..6df93bb86 100644 --- a/projects/v3/src/app/components/team-member-selector/team-member-selector.component.html +++ b/projects/v3/src/app/components/team-member-selector/team-member-selector.component.html @@ -47,7 +47,14 @@

- + + @@ -72,7 +79,13 @@

- + +

Learner's answer diff --git a/projects/v3/src/app/components/team-member-selector/team-member-selector.component.ts b/projects/v3/src/app/components/team-member-selector/team-member-selector.component.ts index 05ee1e5d9..c998c1281 100644 --- a/projects/v3/src/app/components/team-member-selector/team-member-selector.component.ts +++ b/projects/v3/src/app/components/team-member-selector/team-member-selector.component.ts @@ -167,4 +167,14 @@ export class TeamMemberSelectorComponent implements ControlValueAccessor, OnInit return !this.doAssessment && !this.doReview && (this.submissionStatus === 'feedback available' || this.submissionStatus === 'pending review' || (this.submissionStatus === 'done' && this.reviewStatus === '')) && (this.submission?.answer || this.review?.answer); } + + // innerHTML text toggle - submission + onLabelToggle = (id: string): void => { + this.onChange(id); + } + + // innerHTML text toggle - review + onLabelToggleReview = (id: string): void => { + this.onChange(id, 'answer'); + } } diff --git a/projects/v3/src/app/directives/toggle-label/toggle-label.directive.spec.ts b/projects/v3/src/app/directives/toggle-label/toggle-label.directive.spec.ts new file mode 100644 index 000000000..afdb14361 --- /dev/null +++ b/projects/v3/src/app/directives/toggle-label/toggle-label.directive.spec.ts @@ -0,0 +1,81 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Component, DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { ToggleLabelDirective } from './toggle-label.directive'; + +@Component({ + template: ` + + Test Label + + `, + standalone: true, + imports: [ToggleLabelDirective] +}) +class TestComponent { + disabled = false; + toggleFunction = jasmine.createSpy('toggleFunction'); +} + +describe('ToggleLabelDirective', () => { + let component: TestComponent; + let fixture: ComponentFixture; + let spanElement: DebugElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TestComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(TestComponent); + component = fixture.componentInstance; + spanElement = fixture.debugElement.query(By.directive(ToggleLabelDirective)); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + expect(spanElement).toBeTruthy(); + }); + + it('should call toggle function on click', () => { + spanElement.triggerEventHandler('click', { preventDefault: () => {}, stopPropagation: () => {}, target: spanElement.nativeElement }); + expect(component.toggleFunction).toHaveBeenCalledWith('test-id'); + }); + + it('should call toggle function on Enter key', () => { + spanElement.triggerEventHandler('keydown', { key: 'Enter', preventDefault: () => {}, stopPropagation: () => {} }); + expect(component.toggleFunction).toHaveBeenCalledWith('test-id'); + }); + + it('should call toggle function on Space key', () => { + spanElement.triggerEventHandler('keydown', { key: ' ', preventDefault: () => {}, stopPropagation: () => {} }); + expect(component.toggleFunction).toHaveBeenCalledWith('test-id'); + }); + + it('should not call toggle function when disabled', () => { + component.disabled = true; + fixture.detectChanges(); + + spanElement.triggerEventHandler('click', { preventDefault: () => {}, stopPropagation: () => {}, target: spanElement.nativeElement }); + spanElement.triggerEventHandler('keydown', { key: 'Enter', preventDefault: () => {}, stopPropagation: () => {} }); + + expect(component.toggleFunction).not.toHaveBeenCalled(); + }); + + it('should not call toggle function when clicking on a link', () => { + const mockEvent = { + preventDefault: () => {}, + stopPropagation: () => {}, + target: { + closest: (selector: string) => selector === 'a' ? {} : null + } + }; + + spanElement.triggerEventHandler('click', mockEvent); + expect(component.toggleFunction).not.toHaveBeenCalled(); + }); +}); diff --git a/projects/v3/src/app/directives/toggle-label/toggle-label.directive.ts b/projects/v3/src/app/directives/toggle-label/toggle-label.directive.ts new file mode 100644 index 000000000..f499a7565 --- /dev/null +++ b/projects/v3/src/app/directives/toggle-label/toggle-label.directive.ts @@ -0,0 +1,31 @@ +import { Directive, HostListener, Input } from '@angular/core'; + +@Directive({ + selector: '[toggleLabel]', + standalone: true +}) +export class ToggleLabelDirective { + @Input('toggleLabel') toggleFn!: (id: string) => void; + @Input() toggleId!: string; + @Input() toggleDisabled = false; + + @HostListener('click', ['$event']) + onClick(ev: MouseEvent) { + if (this.toggleDisabled) return; + const el = ev.target as HTMLElement | null; + if (el && el.closest('a')) return; + ev.preventDefault(); + ev.stopPropagation(); + this.toggleFn?.(this.toggleId); + } + + @HostListener('keydown', ['$event']) + onKeydown(ev: KeyboardEvent) { + if (this.toggleDisabled) return; + if (ev.key === 'Enter' || ev.key === ' ') { + ev.preventDefault(); + ev.stopPropagation(); + this.toggleFn?.(this.toggleId); + } + } +} From 5be60c02fdd36833f6adc993a4ca2e203e54ecea Mon Sep 17 00:00:00 2001 From: trtshen Date: Thu, 18 Sep 2025 19:15:27 +0800 Subject: [PATCH 4/7] [CORE-8012] support empty submission for slider now --- .../assessment/assessment.component.html | 30 ++++--- .../assessment/assessment.component.ts | 23 +++-- .../multi-team-member-selector.component.ts | 1 - .../components/slider/slider.component.html | 90 ++++++++++++------- .../app/components/slider/slider.component.ts | 13 +++ projects/v3/src/styles.scss | 8 +- 6 files changed, 109 insertions(+), 56 deletions(-) diff --git a/projects/v3/src/app/components/assessment/assessment.component.html b/projects/v3/src/app/components/assessment/assessment.component.html index 7c77db166..428ab710c 100644 --- a/projects/v3/src/app/components/assessment/assessment.component.html +++ b/projects/v3/src/app/components/assessment/assessment.component.html @@ -203,19 +203,21 @@ [@tickAnimation]="failed()[question.id] ? 'visible' : 'hidden'">

- - - No answer for this question. - + + + + No answer for this question. + + -
\ No newline at end of file + diff --git a/projects/v3/src/app/components/assessment/assessment.component.ts b/projects/v3/src/app/components/assessment/assessment.component.ts index c01215fb9..9f83c3160 100644 --- a/projects/v3/src/app/components/assessment/assessment.component.ts +++ b/projects/v3/src/app/components/assessment/assessment.component.ts @@ -514,17 +514,26 @@ Best regards`; } } + + let quesCtrl: { answer: any; comment?: string; file?: any } | any = null; + // multiple initial answer - let answer: string | object | any[] = ''; if (question.type === 'multi team member selector') { - answer = []; + quesCtrl = { answer: [] }; + } + + if (this.action === 'review') { + quesCtrl.comment = ''; + quesCtrl.answer = ''; + quesCtrl.file = null; + + // multiple initial answer + if (question.type === 'multi team member selector') { + quesCtrl.answer = []; + } } - this.questionsForm.addControl('q-' + question.id, new FormControl({ - answer, - comment: '', - file: null, - }, validator)); + this.questionsForm.addControl('q-' + question.id, new FormControl(quesCtrl, validator)); }); }); diff --git a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts index 07ac2595c..baf4795b9 100644 --- a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts +++ b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts @@ -110,7 +110,6 @@ export class MultiTeamMemberSelectorComponent implements ControlValueAccessor, O if (!this.innerValue) { this.innerValue = { answer: [], - comment: '' }; } diff --git a/projects/v3/src/app/components/slider/slider.component.html b/projects/v3/src/app/components/slider/slider.component.html index b88506f83..63527b90f 100644 --- a/projects/v3/src/app/components/slider/slider.component.html +++ b/projects/v3/src/app/components/slider/slider.component.html @@ -2,45 +2,69 @@

{{question
- - + +
+ + - -
-
- + +
+
+ +
- -
- - - Learner's answer: {{ getChoiceNameById(submission.answer) }} - - - - Expert's answer: {{ getChoiceNameById(review.answer) }} - + + +
+ + + +

No answer provided

+

The learner has not submitted an answer for this question.

+
+
+
+
+ + +
+ +
+ + + Learner's answer: {{ getChoiceNameById(submission.answer) }} + + + + + Expert's answer: {{ getChoiceNameById(review.answer) }} + +
+ + +
+ + + No answers available yet + +
- - +