From 23f92b7898e85d2b71d6fadf9fe58e9eea823e20 Mon Sep 17 00:00:00 2001 From: domoberzin <74132255+domoberzin@users.noreply.github.com> Date: Sat, 23 Sep 2023 10:37:52 +0800 Subject: [PATCH 1/2] [#12329] Refactoring of sortable tables - Student list (#12539) --- .../InstructorCourseDetailsPageE2ETest.java | 2 +- ...nsion-confirm-modal.component.spec.ts.snap | 12 +- .../sortable-table.component.html | 6 +- .../sortable-table.component.ts | 8 + .../student-list.component.spec.ts.snap | 4283 +++++++++-------- .../cell-with-actions.component.html | 66 + .../cell-with-actions.component.ts | 46 + .../student-list/student-list.component.html | 138 +- .../student-list.component.spec.ts | 28 +- .../student-list/student-list.component.ts | 139 +- .../student-list/student-list.module.ts | 7 +- ...course-details-page.component.spec.ts.snap | 357 +- ...tructor-course-details-page.component.html | 1 - ...tructor-search-page.component.spec.ts.snap | 714 +-- .../student-result-table.component.html | 1 - ...nstructor-student-list-page.component.html | 1 - 16 files changed, 3298 insertions(+), 2511 deletions(-) create mode 100644 src/web/app/components/student-list/cell-with-actions.component.html create mode 100644 src/web/app/components/student-list/cell-with-actions.component.ts diff --git a/src/e2e/java/teammates/e2e/cases/InstructorCourseDetailsPageE2ETest.java b/src/e2e/java/teammates/e2e/cases/InstructorCourseDetailsPageE2ETest.java index f229c17cf9e..49fa371e2da 100644 --- a/src/e2e/java/teammates/e2e/cases/InstructorCourseDetailsPageE2ETest.java +++ b/src/e2e/java/teammates/e2e/cases/InstructorCourseDetailsPageE2ETest.java @@ -116,7 +116,7 @@ public void testAll() { ______TS("delete student"); detailsPage.sortByName(); detailsPage.sortByStatus(); - StudentAttributes[] studentsAfterDelete = { students[3], students[0], students[1] }; + StudentAttributes[] studentsAfterDelete = { students[0], students[3], students[1] }; detailsPage.deleteStudent(student); detailsPage.verifyStatusMessage("Student is successfully deleted from course \"" diff --git a/src/web/app/components/extension-confirm-modal/__snapshots__/extension-confirm-modal.component.spec.ts.snap b/src/web/app/components/extension-confirm-modal/__snapshots__/extension-confirm-modal.component.spec.ts.snap index 5820c11b56e..13d30c3cc91 100644 --- a/src/web/app/components/extension-confirm-modal/__snapshots__/extension-confirm-modal.component.spec.ts.snap +++ b/src/web/app/components/extension-confirm-modal/__snapshots__/extension-confirm-modal.component.spec.ts.snap @@ -204,7 +204,9 @@ exports[`ExtensionConfirmModalComponent should snap with the extended students a testStudent1@gmail.com - 5 Apr 2000 2:00:00 + + 5 Apr 2000 2:00:00 + @@ -221,7 +223,9 @@ exports[`ExtensionConfirmModalComponent should snap with the extended students a testStudent2@gmail.com - 5 Apr 2000 2:00:00 + + 5 Apr 2000 2:00:00 + @@ -238,7 +242,9 @@ exports[`ExtensionConfirmModalComponent should snap with the extended students a testStudent3@gmail.com - 5 Apr 2000 2:00:00 + + 5 Apr 2000 2:00:00 + diff --git a/src/web/app/components/sortable-table/sortable-table.component.html b/src/web/app/components/sortable-table/sortable-table.component.html index 78cdb7cb72c..aa4c9da3874 100644 --- a/src/web/app/components/sortable-table/sortable-table.component.html +++ b/src/web/app/components/sortable-table/sortable-table.component.html @@ -1,6 +1,6 @@ - + diff --git a/src/web/app/components/sortable-table/sortable-table.component.ts b/src/web/app/components/sortable-table/sortable-table.component.ts index 957479721ac..0a3e5a983f6 100644 --- a/src/web/app/components/sortable-table/sortable-table.component.ts +++ b/src/web/app/components/sortable-table/sortable-table.component.ts @@ -15,6 +15,11 @@ export enum SortableTableHeaderColorScheme { * White background with black text. */ WHITE, + + /** + * Custom background setting + */ + OTHERS, } /** @@ -72,6 +77,9 @@ export class SortableTableComponent implements OnInit, OnChanges { @Input() headerColorScheme: SortableTableHeaderColorScheme = SortableTableHeaderColorScheme.BLUE; + @Input() + customHeaderStyle: string = ''; + @Input() columns: ColumnData[] = []; diff --git a/src/web/app/components/student-list/__snapshots__/student-list.component.spec.ts.snap b/src/web/app/components/student-list/__snapshots__/student-list.component.spec.ts.snap index baae9377186..5a0c68a58db 100644 --- a/src/web/app/components/student-list/__snapshots__/student-list.component.spec.ts.snap +++ b/src/web/app/components/student-list/__snapshots__/student-list.component.spec.ts.snap @@ -5,15 +5,21 @@ exports[`StudentListComponent should snap with default fields 1`] = ` JoinState={[Function Object]} SortBy={[Function Object]} SortOrder={[Function Object]} + SortableTableHeaderColorScheme={[Function Object]} __ngContext__={[Function Number]} + columnsData={[Function Array]} courseId="" courseService={[Function CourseService]} + customHeaderStyle={[Function String]} enableRemindButton="false" + headerColorScheme={[Function Number]} isActionButtonsEnabled={[Function Boolean]} isHideTableHead="false" listOfStudentsToHide={[Function Array]} removeStudentFromCourseEvent={[Function EventEmitter_]} + rowsData={[Function Array]} searchString="" + searchTermsHighlighterPipe={[Function SearchTermsHighlighterPipe]} simpleModalService={[Function SimpleModalService]} sortStudentListEvent={[Function EventEmitter_]} statusMessageService={[Function StatusMessageService]} @@ -22,90 +28,146 @@ exports[`StudentListComponent should snap with default fields 1`] = ` tableSortOrder={[Function Number]} useGrayHeading={[Function Boolean]} > -
-
@@ -25,7 +25,9 @@ [ngComponentOutlet]="item.customComponent.component" [ndcDynamicInputs]="item.customComponent.componentData(idx)" > - {{ item.displayValue }} + + + {{ item.value }}
- + +
- - - - - + - + - + + + - - - - - -
+
- + + - - + + - - + + + + + - - - Action(s) -
+ Action(s) + + + + + + + + `; @@ -115,15 +177,21 @@ exports[`StudentListComponent should snap with enable remind button set to true JoinState={[Function Object]} SortBy={[Function Object]} SortOrder={[Function Object]} + SortableTableHeaderColorScheme={[Function Object]} __ngContext__={[Function Number]} + columnsData={[Function Array]} courseId="" courseService={[Function CourseService]} + customHeaderStyle={[Function String]} enableRemindButton={[Function Boolean]} + headerColorScheme={[Function Number]} isActionButtonsEnabled={[Function Boolean]} isHideTableHead="false" listOfStudentsToHide={[Function Array]} removeStudentFromCourseEvent={[Function EventEmitter_]} + rowsData={[Function Array]} searchString="" + searchTermsHighlighterPipe={[Function SearchTermsHighlighterPipe]} simpleModalService={[Function SimpleModalService]} sortStudentListEvent={[Function EventEmitter_]} statusMessageService={[Function StatusMessageService]} @@ -132,342 +200,426 @@ exports[`StudentListComponent should snap with enable remind button set to true tableSortOrder={[Function Number]} useGrayHeading={[Function Boolean]} > -
- - + +
- - - - - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
- + + - - + + - - + + - - + + - - Action(s) -
- Tutorial Group 1 - - Team 1 - - tester - - Yet to Join - - tester@tester.com - - - View - - - Edit - - - - - All Records - -
- Tutorial Group 1 - - Team 1 - - Benny Charles - - Yet to Join - - benny.c.tmms@gmail.tmt - - - View - - - Edit - - - - - All Records - -
- Tutorial Group 2 - - Team 1 - - Alice Betsy - - Joined - - alice.b.tmms@gmail.tmt - - + Email + + + + + - View - - - Edit - - - - All Records - - -
- Tutorial Group 2 - - Team 1 - - Danny Engrid - - Joined - - danny.e.tmms@gmail.tmt - - - View - - - Edit - - - - All Records - -
+ + Action(s) + + + + + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + tester + + + + Yet to Join + + + + tester@tester.com + + + + + +
+ + View + + + Edit + + + + + All Records + +
+
+ + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + Benny Charles + + + + Yet to Join + + + + benny.c.tmms@gmail.tmt + + + + + +
+ + View + + + Edit + + + + + All Records + +
+
+ + + + + + Tutorial Group 2 + + + + + Team 1 + + + + + Alice Betsy + + + + Joined + + + + alice.b.tmms@gmail.tmt + + + + + + + + + + + + + Tutorial Group 2 + + + + + Team 1 + + + + + Danny Engrid + + + + Joined + + + + danny.e.tmms@gmail.tmt + + + + + + + + + + + +
`; @@ -477,15 +629,21 @@ exports[`StudentListComponent should snap with enable remind button set to true, JoinState={[Function Object]} SortBy={[Function Object]} SortOrder={[Function Object]} + SortableTableHeaderColorScheme={[Function Object]} __ngContext__={[Function Number]} + columnsData={[Function Array]} courseId="" courseService={[Function CourseService]} + customHeaderStyle={[Function String]} enableRemindButton={[Function Boolean]} + headerColorScheme={[Function Number]} isActionButtonsEnabled={[Function Boolean]} isHideTableHead="false" listOfStudentsToHide={[Function Array]} removeStudentFromCourseEvent={[Function EventEmitter_]} + rowsData={[Function Array]} searchString="" + searchTermsHighlighterPipe={[Function SearchTermsHighlighterPipe]} simpleModalService={[Function SimpleModalService]} sortStudentListEvent={[Function EventEmitter_]} statusMessageService={[Function StatusMessageService]} @@ -494,336 +652,420 @@ exports[`StudentListComponent should snap with enable remind button set to true, tableSortOrder={[Function Number]} useGrayHeading={[Function Boolean]} > -
- - + +
- - - - - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
- + + - - + + - - + + - - + + - - Action(s) -
- Tutorial Group 1 - - Team 1 - - tester - - Yet to Join - - tester@tester.com - - - View - - - Edit - - - - - All Records - -
- Tutorial Group 1 - - Team 1 - - Benny Charles - - Joined - - benny.c.tmms@gmail.tmt - - - View - - - Edit - - - - All Records - -
- Tutorial Group 2 - - Team 1 - - Alice Betsy - - Joined - - alice.b.tmms@gmail.tmt - - - View - - - Edit - - - - All Records - -
- Tutorial Group 2 - - Team 1 - - Danny Engrid - - Joined - - danny.e.tmms@gmail.tmt - - - View - - - Edit - - + + - Delete - - - All Records - - -
+ + Action(s) + + + + + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + tester + + + + Yet to Join + + + + tester@tester.com + + + + + +
+ + View + + + Edit + + + + + All Records + +
+
+ + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + Benny Charles + + + + Joined + + + + benny.c.tmms@gmail.tmt + + + + + + + + + + + + + Tutorial Group 2 + + + + + Team 1 + + + + + Alice Betsy + + + + Joined + + + + alice.b.tmms@gmail.tmt + + + + + + + + + + + + + Tutorial Group 2 + + + + + Team 1 + + + + + Danny Engrid + + + + Joined + + + + danny.e.tmms@gmail.tmt + + + + + + + + + + + +
`; @@ -833,15 +1075,21 @@ exports[`StudentListComponent should snap with some student list data 1`] = ` JoinState={[Function Object]} SortBy={[Function Object]} SortOrder={[Function Object]} + SortableTableHeaderColorScheme={[Function Object]} __ngContext__={[Function Number]} + columnsData={[Function Array]} courseId="" courseService={[Function CourseService]} + customHeaderStyle={[Function String]} enableRemindButton="false" + headerColorScheme={[Function Number]} isActionButtonsEnabled={[Function Boolean]} isHideTableHead="false" listOfStudentsToHide={[Function Array]} removeStudentFromCourseEvent={[Function EventEmitter_]} + rowsData={[Function Array]} searchString="" + searchTermsHighlighterPipe={[Function SearchTermsHighlighterPipe]} simpleModalService={[Function SimpleModalService]} sortStudentListEvent={[Function EventEmitter_]} statusMessageService={[Function StatusMessageService]} @@ -850,330 +1098,414 @@ exports[`StudentListComponent should snap with some student list data 1`] = ` tableSortOrder={[Function Number]} useGrayHeading={[Function Boolean]} > -
- - + +
- - - - - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
- + + - - + + - - + + - - + + - - Action(s) -
- Tutorial Group 1 - - Team 1 - - tester - - Joined - - tester@tester.com - - - View - - - Edit - - - - All Records - -
- Tutorial Group 1 - - Team 1 - - Benny Charles - - Joined - - benny.c.tmms@gmail.tmt - - - View - - - Edit - - - - All Records - -
- Tutorial Group 2 - - Team 1 - - Alice Betsy - - Joined - - alice.b.tmms@gmail.tmt - - - View - - - Edit - - - - All Records - -
- Tutorial Group 2 - - Team 1 - - Danny Engrid - - Joined - - danny.e.tmms@gmail.tmt - - - View - - - Edit - - - + Email + + + + + - All Records - - -
+ + Action(s) + + + + + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + tester + + + + Joined + + + + tester@tester.com + + + + + + + + + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + Benny Charles + + + + Joined + + + + benny.c.tmms@gmail.tmt + + + + + + + + + + + + + Tutorial Group 2 + + + + + Team 1 + + + + + Alice Betsy + + + + Joined + + + + alice.b.tmms@gmail.tmt + + + + + + + + + + + + + Tutorial Group 2 + + + + + Team 1 + + + + + Danny Engrid + + + + Joined + + + + danny.e.tmms@gmail.tmt + + + + + + + + + + + +
`; @@ -1183,15 +1515,21 @@ exports[`StudentListComponent should snap with some student list data and some s JoinState={[Function Object]} SortBy={[Function Object]} SortOrder={[Function Object]} + SortableTableHeaderColorScheme={[Function Object]} __ngContext__={[Function Number]} + columnsData={[Function Array]} courseId="" courseService={[Function CourseService]} + customHeaderStyle={[Function String]} enableRemindButton="false" + headerColorScheme={[Function Number]} isActionButtonsEnabled={[Function Boolean]} isHideTableHead="false" listOfStudentsToHide={[Function Array]} removeStudentFromCourseEvent={[Function EventEmitter_]} + rowsData={[Function Array]} searchString="" + searchTermsHighlighterPipe={[Function SearchTermsHighlighterPipe]} simpleModalService={[Function SimpleModalService]} sortStudentListEvent={[Function EventEmitter_]} statusMessageService={[Function StatusMessageService]} @@ -1200,332 +1538,280 @@ exports[`StudentListComponent should snap with some student list data and some s tableSortOrder={[Function Number]} useGrayHeading={[Function Boolean]} > -
- - + +
- - - - - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
- + + - - + + - - + + - - + + - - Action(s) -
- Tutorial Group 1 - - Team 1 - - Benny Charles - - Joined - - benny.c.tmms@gmail.tmt - - + Email + + + + + - View - - - Edit - - - - All Records - - -
- Tutorial Group 2 - - Team 1 - - Danny Engrid - - Joined - - danny.e.tmms@gmail.tmt - - - View - - - Edit - - - - All Records - -
+ + Action(s) + + + + + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + Benny Charles + + + + Joined + + + + benny.c.tmms@gmail.tmt + + + + + + + + + + + + + Tutorial Group 2 + + + + + Team 1 + + + + + Danny Engrid + + + + Joined + + + + danny.e.tmms@gmail.tmt + + + + + + + + + + + +
`; @@ -1535,15 +1821,21 @@ exports[`StudentListComponent should snap with some student list data when not a JoinState={[Function Object]} SortBy={[Function Object]} SortOrder={[Function Object]} + SortableTableHeaderColorScheme={[Function Object]} __ngContext__={[Function Number]} + columnsData={[Function Array]} courseId="" courseService={[Function CourseService]} + customHeaderStyle={[Function String]} enableRemindButton="false" + headerColorScheme={[Function Number]} isActionButtonsEnabled={[Function Boolean]} isHideTableHead="false" listOfStudentsToHide={[Function Array]} removeStudentFromCourseEvent={[Function EventEmitter_]} + rowsData={[Function Array]} searchString="" + searchTermsHighlighterPipe={[Function SearchTermsHighlighterPipe]} simpleModalService={[Function SimpleModalService]} sortStudentListEvent={[Function EventEmitter_]} statusMessageService={[Function StatusMessageService]} @@ -1552,330 +1844,414 @@ exports[`StudentListComponent should snap with some student list data when not a tableSortOrder={[Function Number]} useGrayHeading={[Function Boolean]} > -
- - + +
- - - - - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
- + + - - + + - - + + - - + + - - Action(s) -
- Tutorial Group 1 - - Team 1 - - tester - - Joined - - tester@tester.com - - - View - - - Edit - - - - All Records - -
- Tutorial Group 1 - - Team 1 - - Benny Charles - - Joined - - benny.c.tmms@gmail.tmt - - - View - - - Edit - - - - All Records - -
- Tutorial Group 2 - - Team 1 - - Alice Betsy - - Joined - - alice.b.tmms@gmail.tmt - - - View - - - Edit - - - - All Records - -
- Tutorial Group 2 - - Team 1 - - Danny Engrid - - Joined - - danny.e.tmms@gmail.tmt - - - View - - - Edit - - + + - Delete - - - All Records - - -
+ + Action(s) + + + + + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + tester + + + + Joined + + + + tester@tester.com + + + + + + + + + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + Benny Charles + + + + Joined + + + + benny.c.tmms@gmail.tmt + + + + + + + + + + + + + Tutorial Group 2 + + + + + Team 1 + + + + + Alice Betsy + + + + Joined + + + + alice.b.tmms@gmail.tmt + + + + + + + + + + + + + Tutorial Group 2 + + + + + Team 1 + + + + + Danny Engrid + + + + Joined + + + + danny.e.tmms@gmail.tmt + + + + + + + + + + + +
`; @@ -1885,15 +2261,21 @@ exports[`StudentListComponent should snap with some student list data with no se JoinState={[Function Object]} SortBy={[Function Object]} SortOrder={[Function Object]} + SortableTableHeaderColorScheme={[Function Object]} __ngContext__={[Function Number]} + columnsData={[Function Array]} courseId="" courseService={[Function CourseService]} + customHeaderStyle={[Function String]} enableRemindButton="false" + headerColorScheme={[Function Number]} isActionButtonsEnabled={[Function Boolean]} isHideTableHead="false" listOfStudentsToHide={[Function Array]} removeStudentFromCourseEvent={[Function EventEmitter_]} + rowsData={[Function Array]} searchString="" + searchTermsHighlighterPipe={[Function SearchTermsHighlighterPipe]} simpleModalService={[Function SimpleModalService]} sortStudentListEvent={[Function EventEmitter_]} statusMessageService={[Function StatusMessageService]} @@ -1902,143 +2284,213 @@ exports[`StudentListComponent should snap with some student list data with no se tableSortOrder={[Function Number]} useGrayHeading={[Function Boolean]} > -
- - + +
- - - - - + - + - + - - - - - - - - - - + - -
+
- + + - - + + - - + + - - Action(s) -
- Team 1 - - tester - - Joined - - tester@tester.com - - - View - - - Edit - - - + Status + + + + + + + - All Records - - -
+ + Action(s) + + + + + + + + + None + + + + + Team 1 + + + + + tester + + + + Joined + + + + tester@tester.com + + + + + + + + + + + +
`; @@ -2048,15 +2500,21 @@ exports[`StudentListComponent should snap with table head set to hidden 1`] = ` JoinState={[Function Object]} SortBy={[Function Object]} SortOrder={[Function Object]} + SortableTableHeaderColorScheme={[Function Object]} __ngContext__={[Function Number]} + columnsData={[Function Array]} courseId="" courseService={[Function CourseService]} + customHeaderStyle={[Function String]} enableRemindButton="false" + headerColorScheme={[Function Number]} isActionButtonsEnabled={[Function Boolean]} isHideTableHead={[Function Boolean]} listOfStudentsToHide={[Function Array]} removeStudentFromCourseEvent={[Function EventEmitter_]} + rowsData={[Function Array]} searchString="" + searchTermsHighlighterPipe={[Function SearchTermsHighlighterPipe]} simpleModalService={[Function SimpleModalService]} sortStudentListEvent={[Function EventEmitter_]} statusMessageService={[Function StatusMessageService]} @@ -2065,91 +2523,146 @@ exports[`StudentListComponent should snap with table head set to hidden 1`] = ` tableSortOrder={[Function Number]} useGrayHeading={[Function Boolean]} > -
- - + +
- - - - - + - + - + + + - - - - - -
+
- + + - - + + - - + + + + + - - - Action(s) -
+ Action(s) + + + + + + + +
`; diff --git a/src/web/app/components/student-list/cell-with-actions.component.html b/src/web/app/components/student-list/cell-with-actions.component.html new file mode 100644 index 00000000000..5b85e8c44a9 --- /dev/null +++ b/src/web/app/components/student-list/cell-with-actions.component.html @@ -0,0 +1,66 @@ +
+ + + {{name}} + + + + + + + + + + + + + + +
diff --git a/src/web/app/components/student-list/cell-with-actions.component.ts b/src/web/app/components/student-list/cell-with-actions.component.ts new file mode 100644 index 00000000000..5feda9bb1ed --- /dev/null +++ b/src/web/app/components/student-list/cell-with-actions.component.ts @@ -0,0 +1,46 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { + NgbDropdownModule, + NgbTooltipModule, +} from '@ng-bootstrap/ng-bootstrap'; +import { InstructorPermissionSet } from 'src/web/types/api-request'; +import { AjaxLoadingModule } from '../ajax-loading/ajax-loading.module'; +import { TeammatesRouterModule } from '../teammates-router/teammates-router.module'; + +@Component({ + selector: 'tm-group-buttons', + templateUrl: './cell-with-actions.component.html', + standalone: true, + imports: [ + CommonModule, + TeammatesRouterModule, + AjaxLoadingModule, + NgbDropdownModule, + NgbTooltipModule, + ], +}) + +export class CellWithActionsComponent { + @Input() idx: number = 0; + @Input() courseId: string = ''; + @Input() email: string = ''; + @Input() isSendReminderLoading: boolean = false; + @Input() enableRemindButton: boolean = false; + @Input() isActionButtonsEnabled: boolean = true; + + @Input() instructorPrivileges: InstructorPermissionSet = { + canModifyCourse: false, + canModifyInstructor: false, + canModifySession: false, + canModifyStudent: false, + canViewStudentInSections: false, + canViewSessionInSections: false, + canSubmitSessionInSections: false, + canModifySessionCommentsInSections: false, + }; + + @Input() remindStudentFromCourse: () => void = () => {}; + @Input() removeStudentFromCourse : () => void = () => {}; + +} diff --git a/src/web/app/components/student-list/student-list.component.html b/src/web/app/components/student-list/student-list.component.html index f1bc8b3b045..a92e8fc6b16 100644 --- a/src/web/app/components/student-list/student-list.component.html +++ b/src/web/app/components/student-list/student-list.component.html @@ -1,129 +1,11 @@ -
- - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - Action(s)
{{studentModel.student.joinState | joinState}} - - - {{name}} - - - - - - - - - - -
+
+ +
diff --git a/src/web/app/components/student-list/student-list.component.spec.ts b/src/web/app/components/student-list/student-list.component.spec.ts index a17ab1e3ab3..f9a35f29ed9 100644 --- a/src/web/app/components/student-list/student-list.component.spec.ts +++ b/src/web/app/components/student-list/student-list.component.spec.ts @@ -1,13 +1,14 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; import { RouterTestingModule } from '@angular/router/testing'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { JoinState } from '../../../types/api-output'; import { Pipes } from '../../pipes/pipes.module'; import { TeammatesCommonModule } from '../teammates-common/teammates-common.module'; import { TeammatesRouterModule } from '../teammates-router/teammates-router.module'; -import { JoinStatePipe } from './join-state.pipe'; import { StudentListComponent } from './student-list.component'; +import { StudentListModule } from './student-list.module'; describe('StudentListComponent', () => { let component: StudentListComponent; @@ -15,7 +16,7 @@ describe('StudentListComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [StudentListComponent, JoinStatePipe], + declarations: [StudentListComponent], imports: [ HttpClientTestingModule, TeammatesRouterModule, @@ -23,6 +24,7 @@ describe('StudentListComponent', () => { NgbModule, TeammatesCommonModule, Pipes, + StudentListModule, ], }) .compileComponents(); @@ -49,7 +51,7 @@ describe('StudentListComponent', () => { }); it('should snap with some student list data', () => { - component.students = [ + component.studentModels = [ { student: { name: 'tester', @@ -99,13 +101,12 @@ describe('StudentListComponent', () => { isAllowedToModifyStudent: true, }, ]; - fixture.detectChanges(); expect(fixture).toMatchSnapshot(); }); it('should snap with some student list data when not allowed to modify student for a specific section', () => { - component.students = [ + component.studentModels = [ { student: { name: 'tester', @@ -162,7 +163,7 @@ describe('StudentListComponent', () => { }); it('should snap with enable remind button set to true and two students yet to join', () => { - component.students = [ + component.studentModels = [ { student: { name: 'tester', @@ -221,7 +222,7 @@ describe('StudentListComponent', () => { it('should snap with enable remind button set to true, one student yet to join when not allowed to modify' + ' student', () => { - component.students = [ + component.studentModels = [ { student: { name: 'tester', @@ -279,7 +280,7 @@ describe('StudentListComponent', () => { }); it('should snap with some student list data and some students to hide', () => { - component.students = [ + component.studentModels = [ { student: { name: 'tester', @@ -330,7 +331,7 @@ describe('StudentListComponent', () => { }, ]; - component.listOfStudentsToHide = [ + component.hiddenStudents = [ 'alice.b.tmms@gmail.tmt', 'tester@tester.com', ]; @@ -340,7 +341,7 @@ describe('StudentListComponent', () => { }); it('should snap with some student list data with no sections', () => { - component.students = [ + component.studentModels = [ { student: { name: 'tester', @@ -361,7 +362,7 @@ describe('StudentListComponent', () => { it('should display "Send Invite" button when a student has not joined the course', () => { component.enableRemindButton = true; - component.students = [ + component.studentModels = [ { student: { name: 'tester', @@ -378,9 +379,8 @@ describe('StudentListComponent', () => { fixture.detectChanges(); - const buttons: any = fixture.nativeElement.querySelectorAll('button'); - const sendInviteButton: any = Array.from(buttons) - .find((button: any) => button.firstChild.nodeValue === 'Send Invite'); + const buttons: any = fixture.debugElement.queryAll(By.css('button')); + const sendInviteButton = buttons.find((button : any) => button.nativeElement.textContent.includes('Send Invite')); expect(sendInviteButton).toBeTruthy(); }); }); diff --git a/src/web/app/components/student-list/student-list.component.ts b/src/web/app/components/student-list/student-list.component.ts index 6c31704e35e..cb84576a9d6 100644 --- a/src/web/app/components/student-list/student-list.component.ts +++ b/src/web/app/components/student-list/student-list.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core'; import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { CourseService } from '../../../services/course.service'; import { SimpleModalService } from '../../../services/simple-modal.service'; @@ -6,7 +6,15 @@ import { StatusMessageService } from '../../../services/status-message.service'; import { JoinState, MessageOutput, Student } from '../../../types/api-output'; import { SortBy, SortOrder } from '../../../types/sort-properties'; import { ErrorMessageOutput } from '../../error-message-output'; +import { SearchTermsHighlighterPipe } from '../../pipes/search-terms-highlighter.pipe'; import { SimpleModalType } from '../simple-modal/simple-modal-type'; +import { + ColumnData, + SortableEvent, + SortableTableCellData, + SortableTableHeaderColorScheme, +} from '../sortable-table/sortable-table.component'; +import { CellWithActionsComponent } from './cell-with-actions.component'; /** * Model of row of student data containing details about a student and their section. @@ -25,7 +33,7 @@ export interface StudentListRowModel { templateUrl: './student-list.component.html', styleUrls: ['./student-list.component.scss'], }) -export class StudentListComponent { +export class StudentListComponent implements OnInit { @Input() courseId: string = ''; @Input() useGrayHeading: boolean = true; @Input() listOfStudentsToHide: string[] = []; @@ -36,18 +44,35 @@ export class StudentListComponent { @Input() tableSortBy: SortBy = SortBy.NONE; @Input() tableSortOrder: SortOrder = SortOrder.ASC; @Input() searchString: string = ''; + @Input() headerColorScheme: SortableTableHeaderColorScheme = SortableTableHeaderColorScheme.OTHERS; + @Input() customHeaderStyle: string = 'bg-light'; + + @Input() set studentModels(studentRowModels: StudentListRowModel[]) { + this.students = studentRowModels; + this.setRowData(); + } + + @Input() set hiddenStudents(hiddenStudents: string[]) { + this.listOfStudentsToHide = hiddenStudents; + this.setRowData(); + } @Output() removeStudentFromCourseEvent: EventEmitter = new EventEmitter(); - @Output() sortStudentListEvent: EventEmitter = new EventEmitter(); + @Output() sortStudentListEvent: EventEmitter = new EventEmitter(); + + rowsData: SortableTableCellData[][] = []; + columnsData: ColumnData[] = []; // enum SortBy: typeof SortBy = SortBy; SortOrder: typeof SortOrder = SortOrder; JoinState: typeof JoinState = JoinState; + SortableTableHeaderColorScheme: typeof SortableTableHeaderColorScheme = SortableTableHeaderColorScheme; constructor(private statusMessageService: StatusMessageService, private courseService: CourseService, - private simpleModalService: SimpleModalService) { + private simpleModalService: SimpleModalService, + private searchTermsHighlighterPipe: SearchTermsHighlighterPipe) { } /** @@ -58,6 +83,105 @@ export class StudentListComponent { studentModel.student.sectionName !== 'None')); } + ngOnInit(): void { + this.setRowData(); + this.setColumnData(); + this.setHeaderStyle(); + } + + setHeaderStyle(): void { + this.customHeaderStyle = this.useGrayHeading ? 'bg-light' : 'bg-info'; + } + + setColumnData(): void { + this.columnsData = [ + { + header: 'Section', + sortBy: SortBy.SECTION_NAME, + headerClass: 'sort-by-section', + }, + { + header: 'Team', + sortBy: SortBy.TEAM_NAME, + headerClass: 'sort-by-team', + }, + { + header: 'Student Name', + sortBy: SortBy.RESPONDENT_NAME, + headerClass: 'sort-by-name', + }, + { + header: 'Status', + sortBy: SortBy.JOIN_STATUS, + headerClass: 'sort-by-status', + }, + { + header: 'Email', + sortBy: SortBy.RESPONDENT_EMAIL, + headerClass: 'sort-by-email', + + }, + { + header: 'Action(s)', + alignment: 'center', + }, + ]; + } + + setRowData(): void { + this.rowsData = this.students + .filter((studentModel: StudentListRowModel) => !this.isStudentToHide(studentModel.student.email)) + .map((studentModel: StudentListRowModel) => { + const rowData: SortableTableCellData[] = [ + { + value: studentModel.student.sectionName, + displayValue: this.searchTermsHighlighterPipe.transform(studentModel.student.sectionName, this.searchString), + }, + { + value: studentModel.student.teamName, + displayValue: this.searchTermsHighlighterPipe.transform(studentModel.student.teamName, this.searchString), + }, + { + value: studentModel.student.name, + displayValue: this.searchTermsHighlighterPipe.transform(studentModel.student.name, this.searchString), + }, + { + value: studentModel.student.joinState === JoinState.JOINED ? 'Joined' : 'Yet to Join', + }, + { + value: studentModel.student.email, + displayValue: this.searchTermsHighlighterPipe.transform(studentModel.student.email, this.searchString), + }, + this.createActionsCell(studentModel), + ]; + + return rowData; + }); + } + + createActionsCell(studentModel: StudentListRowModel): SortableTableCellData { + const actionsCell: SortableTableCellData = { + customComponent: { + component: CellWithActionsComponent, + componentData: (idx: number) => ({ + idx, + courseId: this.courseId, + email: studentModel.student.email, + enableRemindButton: studentModel.student.joinState === JoinState.NOT_JOINED, + instructorPrivileges: { + canModifyStudent: studentModel.isAllowedToModifyStudent, + canViewStudentInSections: studentModel.isAllowedToViewStudentInSection, + }, + isActionButtonsEnabled: this.isActionButtonsEnabled, + removeStudentFromCourse: () => this.openDeleteModal(studentModel), + remindStudentFromCourse: () => this.openRemindModal(studentModel), + }), + }, + }; + + return actionsCell; + } + /** * Function to be passed to ngFor, so that students in the list is tracked by email */ @@ -89,6 +213,9 @@ export class StudentListComponent { `Delete student ${studentModel.student.name}?`, SimpleModalType.DANGER, modalContent); modalRef.result.then(() => { this.removeStudentFromCourse(studentModel.student.email); + this.students = this.students.filter((student: StudentListRowModel) => + student.student.email !== studentModel.student.email); + this.setRowData(); }, () => {}); } @@ -124,8 +251,8 @@ export class StudentListComponent { /** * Sorts the student list */ - sortStudentList(by: SortBy): void { - this.sortStudentListEvent.emit(by); + sortStudentListEventHandler(event: { sortBy: SortBy, sortOrder: SortOrder }): void { + this.sortStudentListEvent.emit(event); } getAriaSort(by: SortBy): String { diff --git a/src/web/app/components/student-list/student-list.module.ts b/src/web/app/components/student-list/student-list.module.ts index d0f98c61565..032c7904391 100644 --- a/src/web/app/components/student-list/student-list.module.ts +++ b/src/web/app/components/student-list/student-list.module.ts @@ -3,7 +3,8 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { Pipes } from '../../pipes/pipes.module'; - +import { SearchTermsHighlighterPipe } from '../../pipes/search-terms-highlighter.pipe'; +import { SortableTableModule } from '../sortable-table/sortable-table.module'; import { TeammatesCommonModule } from '../teammates-common/teammates-common.module'; import { TeammatesRouterModule } from '../teammates-router/teammates-router.module'; import { JoinStatePipe } from './join-state.pipe'; @@ -27,6 +28,10 @@ import { StudentListComponent } from './student-list.component'; TeammatesCommonModule, TeammatesRouterModule, Pipes, + SortableTableModule, ], + providers: [ + SearchTermsHighlighterPipe, + ], }) export class StudentListModule { } diff --git a/src/web/app/pages-instructor/instructor-course-details-page/__snapshots__/instructor-course-details-page.component.spec.ts.snap b/src/web/app/pages-instructor/instructor-course-details-page/__snapshots__/instructor-course-details-page.component.spec.ts.snap index 6623e528f35..693fc798d4f 100644 --- a/src/web/app/pages-instructor/instructor-course-details-page/__snapshots__/instructor-course-details-page.component.spec.ts.snap +++ b/src/web/app/pages-instructor/instructor-course-details-page/__snapshots__/instructor-course-details-page.component.spec.ts.snap @@ -394,168 +394,219 @@ exports[`InstructorCourseDetailsPageComponent should snap with a course with one id="student-list" > -
- - + +
- - - - - + - + - + - + + - - - - - - - - - - - - - -
+
- + + - - + + - - + + - - + + + - - - Action(s) -
- Tutorial Group 1 - - Team 1 - - Jamie - - Yet to Join - - jamie@gmail.com - - - View - - - Edit - - - - - All Records - -
+ + + + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + Jamie + + + + Yet to Join + + + + jamie@gmail.com + + + + + +
+ + View + + + Edit + + + + + All Records + +
+
+ + + + +
diff --git a/src/web/app/pages-instructor/instructor-course-details-page/instructor-course-details-page.component.html b/src/web/app/pages-instructor/instructor-course-details-page/instructor-course-details-page.component.html index 27ccfd1b3d2..3f728ecfd78 100644 --- a/src/web/app/pages-instructor/instructor-course-details-page/instructor-course-details-page.component.html +++ b/src/web/app/pages-instructor/instructor-course-details-page/instructor-course-details-page.component.html @@ -77,7 +77,6 @@

Course Details

diff --git a/src/web/app/pages-instructor/instructor-search-page/__snapshots__/instructor-search-page.component.spec.ts.snap b/src/web/app/pages-instructor/instructor-search-page/__snapshots__/instructor-search-page.component.spec.ts.snap index 730ae8b4ddc..445a9c019a1 100644 --- a/src/web/app/pages-instructor/instructor-search-page/__snapshots__/instructor-search-page.component.spec.ts.snap +++ b/src/web/app/pages-instructor/instructor-search-page/__snapshots__/instructor-search-page.component.spec.ts.snap @@ -156,330 +156,414 @@ exports[`InstructorSearchPageComponent should snap with a student table 1`] = ` class="card-body p-0" > -
- - + +
- - - - - + - + - + - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
- + + - - + + - - + + - - + + + - - - Action(s) -
- Tutorial Group 1 - - Team 1 - - tester - - Joined - - tester@tester.com - - - View - - - Edit - - - - All Records - -
- Tutorial Group 1 - - Team 1 - - Benny Charles - - Joined - - benny.c.tmms@gmail.tmt - - - View - - - Edit - - - - All Records - -
- Tutorial Group 1 - - Team 1 - - Alice Betsy - - Joined - - alice.b.tmms@gmail.tmt - - - View - - - Edit - - - - All Records - -
- Tutorial Group 1 - - Team 1 - - Danny Engrid - - Joined - - danny.e.tmms@gmail.tmt - - - View - - - Edit - - - - All Records - -
+ + + + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + tester + + + + Joined + + + + tester@tester.com + + + + + + + + + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + Benny Charles + + + + Joined + + + + benny.c.tmms@gmail.tmt + + + + + + + + + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + Alice Betsy + + + + Joined + + + + alice.b.tmms@gmail.tmt + + + + + + + + + + + + + Tutorial Group 1 + + + + + Team 1 + + + + + Danny Engrid + + + + Joined + + + + danny.e.tmms@gmail.tmt + + + + + + + + + + + +
diff --git a/src/web/app/pages-instructor/instructor-search-page/student-result-table/student-result-table.component.html b/src/web/app/pages-instructor/instructor-search-page/student-result-table/student-result-table.component.html index e52f78cea11..fdf9b101d9b 100644 --- a/src/web/app/pages-instructor/instructor-search-page/student-result-table/student-result-table.component.html +++ b/src/web/app/pages-instructor/instructor-search-page/student-result-table/student-result-table.component.html @@ -9,7 +9,6 @@ [isActionButtonsEnabled]="isActionButtonsEnabled" [tableSortBy]="studentSortBy" [tableSortOrder]="studentSortOrder" [searchString]="searchString" - (sortStudentListEvent)="sortStudentList(course.students, $event)" (removeStudentFromCourseEvent)="removeStudent(course.students, $event)"> diff --git a/src/web/app/pages-instructor/instructor-student-list-page/instructor-student-list-page.component.html b/src/web/app/pages-instructor/instructor-student-list-page/instructor-student-list-page.component.html index 1183ff5fe0c..84803637eab 100644 --- a/src/web/app/pages-instructor/instructor-student-list-page/instructor-student-list-page.component.html +++ b/src/web/app/pages-instructor/instructor-student-list-page/instructor-student-list-page.component.html @@ -73,7 +73,6 @@

Students

[tableSortOrder]="courseTab.studentSortOrder" [useGrayHeading]="false" [enableRemindButton]="true" - (sortStudentListEvent)="sortStudentList(courseTab, $event)" (removeStudentFromCourseEvent)="removeStudentFromCourse(courseTab, $event)"> From fe6a3977f87fb2d963b4e7dc6a1ba685c0ca97fd Mon Sep 17 00:00:00 2001 From: Douglas Lim <97420966+dlimyy@users.noreply.github.com> Date: Mon, 25 Sep 2023 07:24:13 +0800 Subject: [PATCH 2/2] [#12571] Instructors Edit Feedback Session: Instructor is able to edit submission opening time to an earlier timing (#12580) * Fix submission opening time bug * Fix bug where save button is disabled * Add method in DateTimeService to compare dates * Change to triggerModelChange --------- Co-authored-by: Cedric Ong <67156011+cedricongjh@users.noreply.github.com> --- .../session-edit-form.component.html | 2 +- .../session-edit-form.component.spec.ts | 23 ++++++ .../session-edit-form.component.ts | 30 ++++++++ .../instructor-session-edit-page.component.ts | 1 + src/web/services/datetime.service.spec.ts | 73 +++++++++++++++++++ src/web/services/datetime.service.ts | 50 +++++++++++++ 6 files changed, 178 insertions(+), 1 deletion(-) diff --git a/src/web/app/components/session-edit-form/session-edit-form.component.html b/src/web/app/components/session-edit-form/session-edit-form.component.html index ea8d26cf803..7f7a7f9e435 100644 --- a/src/web/app/components/session-edit-form/session-edit-form.component.html +++ b/src/web/app/components/session-edit-form/session-edit-form.component.html @@ -135,7 +135,7 @@
Or
-
diff --git a/src/web/app/components/session-edit-form/session-edit-form.component.spec.ts b/src/web/app/components/session-edit-form/session-edit-form.component.spec.ts index 21bce10a45d..3ee97b6f228 100644 --- a/src/web/app/components/session-edit-form/session-edit-form.component.spec.ts +++ b/src/web/app/components/session-edit-form/session-edit-form.component.spec.ts @@ -1,6 +1,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { TimeFormat } from 'src/web/types/datetime-const'; import { TeammatesRouterModule } from '../teammates-router/teammates-router.module'; import { SessionEditFormComponent } from './session-edit-form.component'; import { SessionEditFormModule } from './session-edit-form.module'; @@ -30,4 +31,26 @@ describe('SessionEditFormComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should configure the time to be 23:59 if the hour is 23 and minute is greater than 0', () => { + const time : TimeFormat = { hour: 23, minute: 5 }; + component.configureSubmissionOpeningTime(time); + expect(time.hour).toEqual(23); + expect(time.minute).toEqual(59); + }); + + it('should configure the time correctly if the hour is less than 23 and minute is greater than 0', () => { + const time : TimeFormat = { hour: 22, minute: 5 }; + component.configureSubmissionOpeningTime(time); + expect(time.hour).toEqual(23); + expect(time.minute).toEqual(0); + }); + + it('should configure the time correctly if the minute is 0', () => { + const time : TimeFormat = { hour: 21, minute: 0 }; + component.configureSubmissionOpeningTime(time); + expect(time.hour).toEqual(21); + expect(time.minute).toEqual(0); + }); + }); diff --git a/src/web/app/components/session-edit-form/session-edit-form.component.ts b/src/web/app/components/session-edit-form/session-edit-form.component.ts index 1a90937b16e..56e5dfb96f8 100644 --- a/src/web/app/components/session-edit-form/session-edit-form.component.ts +++ b/src/web/app/components/session-edit-form/session-edit-form.component.ts @@ -134,6 +134,36 @@ export class SessionEditFormComponent { }); } + /** + * Triggers the change of the model when the submission opening date changes. + */ + triggerSubmissionOpeningDateModelChange(field: string, date: DateFormat): void { + const minDate: DateFormat = this.minDateForSubmissionStart; + const minTime: TimeFormat = this.minTimeForSubmissionStart; + + // Case where date is same as earliest date and time is earlier than earliest possible time + if (DateTimeService.compareDateFormat(date, minDate) === 0 + && DateTimeService.compareTimeFormat(this.model.submissionStartTime, minTime) === -1) { + this.configureSubmissionOpeningTime(minTime); + this.model.submissionStartTime = minTime; + } + + this.triggerModelChange(field, date); + } + + /** + * Configures the time for the submission opening time. + */ + configureSubmissionOpeningTime(time : TimeFormat) : void { + if (time.hour === 23 && time.minute > 0) { + time.minute = 59; + } else if (time.hour < 23 && time.minute > 0) { + // Case where minutes is not 0 since the earliest time with 0 minutes is the hour before + time.hour += 1; + time.minute = 0; + } + } + /** * Handles course Id change event. * diff --git a/src/web/app/pages-instructor/instructor-session-edit-page/instructor-session-edit-page.component.ts b/src/web/app/pages-instructor/instructor-session-edit-page/instructor-session-edit-page.component.ts index 921fc82419d..4cc6488ff15 100644 --- a/src/web/app/pages-instructor/instructor-session-edit-page/instructor-session-edit-page.component.ts +++ b/src/web/app/pages-instructor/instructor-session-edit-page/instructor-session-edit-page.component.ts @@ -419,6 +419,7 @@ export class InstructorSessionEditPageComponent extends InstructorSessionBasePag this.statusMessageService.showSuccessToast('The feedback session has been updated.'); }, error: (resp: ErrorMessageOutput) => { + this.sessionEditFormModel.isEditable = true; this.statusMessageService.showErrorToast(resp.error.message); }, }); diff --git a/src/web/services/datetime.service.spec.ts b/src/web/services/datetime.service.spec.ts index b97421b4986..1dee9dfb5b5 100644 --- a/src/web/services/datetime.service.spec.ts +++ b/src/web/services/datetime.service.spec.ts @@ -1,5 +1,6 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; +import { DateFormat, TimeFormat } from '../types/datetime-const'; import { DateTimeService } from './datetime.service'; import { TimezoneService } from './timezone.service'; @@ -77,4 +78,76 @@ describe('DateTimeService', () => { expect(result.minute).toEqual(59); }); + it('should return 1 if the first date\'s year is later than the second date\'s year', () => { + const firstDate : DateFormat = { year: 2023, month: 8, day: 23 }; + const secondDate: DateFormat = { year: 2022, month: 8, day: 23 }; + expect(DateTimeService.compareDateFormat(firstDate, secondDate)).toEqual(1); + }); + + it('should return -1 if the first date\'s year is earlier than the second date\'s year', () => { + const firstDate : DateFormat = { year: 2022, month: 8, day: 23 }; + const secondDate: DateFormat = { year: 2023, month: 8, day: 23 }; + expect(DateTimeService.compareDateFormat(firstDate, secondDate)).toEqual(-1); + }); + + it('should return 1 if year is the same and first date\'s month is later than second date\'s month', () => { + const firstDate : DateFormat = { year: 2023, month: 9, day: 23 }; + const secondDate: DateFormat = { year: 2023, month: 8, day: 23 }; + expect(DateTimeService.compareDateFormat(firstDate, secondDate)).toEqual(1); + }); + + it('should return -1 if if year is the same and first date\'s month is earlier than second date\'s month', () => { + const firstDate : DateFormat = { year: 2023, month: 8, day: 23 }; + const secondDate: DateFormat = { year: 2023, month: 9, day: 23 }; + expect(DateTimeService.compareDateFormat(firstDate, secondDate)).toEqual(-1); + }); + + it('should return 1 if year and month are the same and first date\'s day is later than second date\'s day', () => { + const firstDate : DateFormat = { year: 2023, month: 9, day: 28 }; + const secondDate: DateFormat = { year: 2023, month: 9, day: 23 }; + expect(DateTimeService.compareDateFormat(firstDate, secondDate)).toEqual(1); + }); + + it('should return -1 if year and month are same and first date\'s day is earlier than second date\'s day', () => { + const firstDate : DateFormat = { year: 2023, month: 9, day: 23 }; + const secondDate: DateFormat = { year: 2023, month: 9, day: 28 }; + expect(DateTimeService.compareDateFormat(firstDate, secondDate)).toEqual(-1); + }); + + it('should return 0 if both dates have the same year and month and day', () => { + const firstDate : DateFormat = { year: 2023, month: 9, day: 28 }; + const secondDate: DateFormat = { year: 2023, month: 9, day: 28 }; + expect(DateTimeService.compareDateFormat(firstDate, secondDate)).toEqual(0); + }); + + it('should return 1 if the first timing\'s hour is later than the second timing\'s hour', () => { + const firstTime : TimeFormat = { hour: 21, minute: 0 }; + const secondTime : TimeFormat = { hour: 19, minute: 0 }; + expect(DateTimeService.compareTimeFormat(firstTime, secondTime)).toEqual(1); + }); + + it('should return -1 if the first timing\'s hour is earlier than the second timing\'s hour', () => { + const firstTime : TimeFormat = { hour: 20, minute: 0 }; + const secondTime : TimeFormat = { hour: 21, minute: 0 }; + expect(DateTimeService.compareTimeFormat(firstTime, secondTime)).toEqual(-1); + }); + + it('should return 1 if hour is the same and first timing\'s minute is later than second timing\'s minute', () => { + const firstTime : TimeFormat = { hour: 21, minute: 30 }; + const secondTime : TimeFormat = { hour: 21, minute: 0 }; + expect(DateTimeService.compareTimeFormat(firstTime, secondTime)).toEqual(1); + }); + + it('should return -1 if hour is same and first timing\'s minute is earlier than second timing\'s minute', () => { + const firstTime : TimeFormat = { hour: 21, minute: 0 }; + const secondTime : TimeFormat = { hour: 21, minute: 30 }; + expect(DateTimeService.compareTimeFormat(firstTime, secondTime)).toEqual(-1); + }); + + it('should return 0 if both timings have the same hour and minute', () => { + const firstTime : TimeFormat = { hour: 21, minute: 30 }; + const secondTime : TimeFormat = { hour: 21, minute: 30 }; + expect(DateTimeService.compareTimeFormat(firstTime, secondTime)).toEqual(0); + }); + }); diff --git a/src/web/services/datetime.service.ts b/src/web/services/datetime.service.ts index 9baef02632c..b6d86fcd290 100644 --- a/src/web/services/datetime.service.ts +++ b/src/web/services/datetime.service.ts @@ -123,4 +123,54 @@ export class DateTimeService { minute: mmt.minute(), }; } + + /** + * Compares the first date with the second date and checks whether the first + * date is earlier, same or later than the second date. + * Returns 1 if the first date is later than second date, 0 if the first date is the + * same as the second date and -1 if the first date is earlier than the second date. + */ + static compareDateFormat(firstDate : DateFormat, secondDate : DateFormat) : number { + if (firstDate.year > secondDate.year) { + return 1; + } + if (firstDate.year < secondDate.year) { + return -1; + } + if (firstDate.month > secondDate.month) { + return 1; + } + if (firstDate.month < secondDate.month) { + return -1; + } + if (firstDate.day > secondDate.day) { + return 1; + } + if (firstDate.day < secondDate.day) { + return -1; + } + return 0; + } + + /** + * Compares the first timing with the second timing and checks whether the first + * timing is earlier, same or later than the second timing. + * Returns 1 if the first timing is later than second timing, 0 if the first timing is the + * same as the second timing and -1 if the first timing is earlier than the second timing. + */ + static compareTimeFormat(firstTiming : TimeFormat, secondTiming : TimeFormat) : number { + if (firstTiming.hour > secondTiming.hour) { + return 1; + } + if (firstTiming.hour < secondTiming.hour) { + return -1; + } + if (firstTiming.minute > secondTiming.minute) { + return 1; + } + if (firstTiming.minute < secondTiming.minute) { + return -1; + } + return 0; + } }