diff --git a/compose.dev.yml b/compose.dev.yml index cb9433736b..ceb93c8369 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -48,6 +48,10 @@ services: - /app/node_modules - ./services/collaboration:/app + collaboration-db: + ports: + - 27020:27017 + history: command: npm run dev ports: diff --git a/frontend/src/_services/history.service.ts b/frontend/src/_services/history.service.ts index 3ee0240ca3..41b5a71930 100644 --- a/frontend/src/_services/history.service.ts +++ b/frontend/src/_services/history.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { historyResponse, MatchingHistory } from '../app/account/history/history.model'; +import { historyResponse, MatchingHistory } from '../app/history/history.model'; import { ApiService } from './api.service'; @Injectable({ @@ -22,7 +22,8 @@ export class HistoryService extends ApiService { id: item._id, roomId: item.roomId, collaborator: item.collaborator.username, - question: item.question, + title: item.question.title, + description: item.question.description, topics: item.question.topics, difficulty: item.question.difficulty, status: item.status, diff --git a/frontend/src/app/account/account.component.ts b/frontend/src/app/account/account.component.ts index aa1bc14b19..a2f6e4c0f1 100644 --- a/frontend/src/app/account/account.component.ts +++ b/frontend/src/app/account/account.component.ts @@ -5,7 +5,6 @@ import { LoginComponent } from './login/login.component'; import { RegisterComponent } from './register/register.component'; import { LayoutComponent } from './layout.component'; import { ProfileComponent } from './profile/profile.component'; -import { HistoryComponent } from './history/history.component'; const routes: Routes = [ { @@ -16,7 +15,6 @@ const routes: Routes = [ { path: 'login', component: LoginComponent }, { path: 'register', component: RegisterComponent }, { path: 'profile', component: ProfileComponent }, - { path: 'history', component: HistoryComponent }, ], }, ]; diff --git a/frontend/src/app/account/account.module.ts b/frontend/src/app/account/account.module.ts index 10b87cfb34..33e7cb9efa 100644 --- a/frontend/src/app/account/account.module.ts +++ b/frontend/src/app/account/account.module.ts @@ -7,7 +7,6 @@ import { RegisterComponent } from './register/register.component'; import { LayoutComponent } from './layout.component'; import { AccountRoutingModule } from './account.component'; import { ProfileComponent } from './profile/profile.component'; -import { HistoryComponent } from './history/history.component'; @NgModule({ imports: [ @@ -18,7 +17,6 @@ import { HistoryComponent } from './history/history.component'; LoginComponent, RegisterComponent, ProfileComponent, - HistoryComponent, ], }) export class AccountModule {} diff --git a/frontend/src/app/account/history/history.component.html b/frontend/src/app/account/history/history.component.html deleted file mode 100644 index ed1d597ccf..0000000000 --- a/frontend/src/app/account/history/history.component.html +++ /dev/null @@ -1,84 +0,0 @@ -
- - -
-

Matching History

-
-
- -
- - - - - - -
-
- - - - Question - - - Difficulty - - Topics - - Collaborator - - Status - Time - - - - - {{ history.question.title }} - {{ history.difficulty }} - {{ history.topics.join(', ') }} - {{ history.collaborator }} - - @if (history.status === 'COMPLETED') { - - } @else if (history.status === 'FORFEITED') { - - } @else if (history.status === 'IN_PROGRESS') { - - } - - {{ history.time }} - - -
-
-
-
-

{{ panelHistory?.question?.title }}

- -
-
-

{{ panelHistory?.question?.description }}

-
-
-
- diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 59b77f39aa..2ce0db61b0 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -5,6 +5,7 @@ import { MatchingComponent } from './matching/matching.component'; import { HomeComponent } from './home/home.component'; import { AuthGuardService } from '../_services/auth.guard.service'; import { CollabGuardService } from '../_services/collab.guard.service'; +import { HistoryComponent } from './history/history.component'; const accountModule = () => import('./account/account.module').then(x => x.AccountModule); @@ -33,6 +34,11 @@ export const routes: Routes = [ component: HomeComponent, canActivate: [AuthGuardService], }, + { + path: 'history', + component: HistoryComponent, + canActivate: [AuthGuardService], + }, { path: '**', redirectTo: '/home', diff --git a/frontend/src/app/account/history/history.component.css b/frontend/src/app/history/history.component.css similarity index 82% rename from frontend/src/app/account/history/history.component.css rename to frontend/src/app/history/history.component.css index 2053d5c2d0..155f32abb6 100644 --- a/frontend/src/app/account/history/history.component.css +++ b/frontend/src/app/history/history.component.css @@ -1,3 +1,12 @@ +.container { + min-height: calc(100dvh - 160px); + width: 100%; + justify-content: center; + align-items: center; + padding: 1rem; + margin-top: auto; +} + .sliding-panel { position: fixed; top: 0; @@ -9,6 +18,7 @@ box-shadow: -2px 0 5px rgba(0,0,0,0.5); transition: right 0.3s ease; z-index: 1000; + max-width: 90%; } .sliding-panel.open { diff --git a/frontend/src/app/history/history.component.html b/frontend/src/app/history/history.component.html new file mode 100644 index 0000000000..050c8801c4 --- /dev/null +++ b/frontend/src/app/history/history.component.html @@ -0,0 +1,83 @@ +
+
+ + +
+

Matching History

+ + + + + + +
+
+ + + Question + + Difficulty + + Topics + + Collaborator + + Status + Time + + + + + {{ history.title }} + {{ history.difficulty }} + {{ history.topics.join(', ') }} + {{ history.collaborator }} + + @if (history.status === 'COMPLETED') { + + } @else if (history.status === 'FORFEITED') { + + } @else if (history.status === 'IN_PROGRESS') { + + } + + {{ history.time | date: 'dd/MM/yyyy hh:mm a' }} + + +
+
+ + +

{{ panelHistory?.title }}

+
+
+

{{ panelHistory?.description }}

+

Submitted Solution

+
+
+
+ +
diff --git a/frontend/src/app/account/history/history.component.ts b/frontend/src/app/history/history.component.ts similarity index 53% rename from frontend/src/app/account/history/history.component.ts rename to frontend/src/app/history/history.component.ts index 728a0b8782..f6d10f75aa 100644 --- a/frontend/src/app/account/history/history.component.ts +++ b/frontend/src/app/history/history.component.ts @@ -1,50 +1,61 @@ import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; -import { TableModule } from 'primeng/table'; +import { Table, TableModule } from 'primeng/table'; import { CommonModule, DatePipe } from '@angular/common'; import { HistoryStatus, MatchingHistory } from './history.model'; -import { HistoryService } from '../../../_services/history.service'; -import { MessageService } from 'primeng/api'; +import { HistoryService } from '../../_services/history.service'; +import { MessageService, SortEvent } from 'primeng/api'; import { InputTextModule } from 'primeng/inputtext'; import { ButtonModule } from 'primeng/button'; import { IconFieldModule } from 'primeng/iconfield'; import { InputIconModule } from 'primeng/inputicon'; import { oneDark } from '@codemirror/theme-one-dark'; -import { EditorState } from '@codemirror/state'; +import { EditorState, Extension } from '@codemirror/state'; import { EditorView, basicSetup } from 'codemirror'; -import { languageMap } from '../../collaboration/editor/languages'; +import { languageMap } from '../collaboration/editor/languages'; import { ToastModule } from 'primeng/toast'; +import { SidebarModule } from 'primeng/sidebar'; import { Router } from '@angular/router'; @Component({ standalone: true, - imports: [TableModule, CommonModule, InputTextModule, ButtonModule, IconFieldModule, InputIconModule, ToastModule], + imports: [ + SidebarModule, + TableModule, + CommonModule, + InputTextModule, + ButtonModule, + IconFieldModule, + InputIconModule, + ToastModule, + ], providers: [MessageService, DatePipe], templateUrl: './history.component.html', styleUrl: './history.component.css', }) export class HistoryComponent implements OnInit { @ViewChild('editor') editor!: ElementRef; + @ViewChild('dt') dt!: Table; histories: MatchingHistory[] = []; + initialValue: MatchingHistory[] = []; loading = true; isPanelVisible = false; panelHistory: MatchingHistory | null = null; editorView: EditorView | null = null; + customTheme!: Extension; + isSorted: null | undefined | boolean; constructor( private historyService: HistoryService, private messageService: MessageService, - private datePipe: DatePipe, private router: Router, ) {} ngOnInit() { this.historyService.getHistories().subscribe({ next: data => { - this.histories = data.map(history => ({ - ...history, - time: this.datePipe.transform(history.time, 'short'), // Pipe to format date for searching - })); + this.histories = data; + this.initialValue = [...data]; this.loading = false; }, error: () => { @@ -60,6 +71,33 @@ export class HistoryComponent implements OnInit { }); } + customSort(event: Required) { + event.data?.sort((data1, data2) => { + const value1 = data1[event.field]; + const value2 = data2[event.field]; + let result = 0; + + // Null checks + if (value1 === null && value2 !== null) { + result = -1; + } else if (value1 !== null && value2 === null) { + result = 1; + } else if (value1 === null && value2 === null) { + result = 0; + } else if (event.field == 'time') { + result = new Date(value1) >= new Date(value2) ? 1 : -1; + } else if (typeof value1 === 'string' && typeof value2 === 'string') { + // String comparison + result = value1.localeCompare(value2); + } else { + // Generic comparison for numbers and other types + result = value1 < value2 ? -1 : value1 > value2 ? 1 : 0; + } + + return event.order * result; + }); + } + onRowSelect(history: MatchingHistory) { this.panelHistory = history; if (history.status != HistoryStatus.IN_PROGRESS) { @@ -80,10 +118,29 @@ export class HistoryComponent implements OnInit { this.editorView.destroy(); } + const customTheme = EditorView.theme( + { + '&': { + backgroundColor: 'var(--surface-section)', + }, + '.cm-gutters': { + backgroundColor: 'var(--surface-section)', + }, + }, + { dark: true }, + ); + const languageExtension = languageMap[language] || languageMap['java']; const state = EditorState.create({ doc: code, - extensions: [basicSetup, languageExtension, oneDark, EditorView.editable.of(false)], + extensions: [ + basicSetup, + languageExtension, + customTheme, + oneDark, + EditorView.lineWrapping, + EditorView.editable.of(false), + ], }); this.editorView = new EditorView({ diff --git a/frontend/src/app/account/history/history.model.ts b/frontend/src/app/history/history.model.ts similarity index 91% rename from frontend/src/app/account/history/history.model.ts rename to frontend/src/app/history/history.model.ts index e0972b1411..a10893323e 100644 --- a/frontend/src/app/account/history/history.model.ts +++ b/frontend/src/app/history/history.model.ts @@ -1,4 +1,4 @@ -import { DifficultyLevels } from '../../questions/difficulty-levels.enum'; +import { DifficultyLevels } from '../questions/difficulty-levels.enum'; export enum HistoryStatus { COMPLETED = 'COMPLETED', @@ -10,7 +10,8 @@ export interface MatchingHistory { id: string; roomId: string; collaborator: string; // collaborator username - question: Question; // question + title: string; + description: string; difficulty: DifficultyLevels; // question difficulty topics: string[]; // question topics status: HistoryStatus; // status of the session diff --git a/frontend/src/app/navigation-bar/navigation-bar.component.ts b/frontend/src/app/navigation-bar/navigation-bar.component.ts index 1bf5fb0bce..774cfd5aab 100644 --- a/frontend/src/app/navigation-bar/navigation-bar.component.ts +++ b/frontend/src/app/navigation-bar/navigation-bar.component.ts @@ -71,7 +71,7 @@ export class NavigationBarComponent implements OnInit { { label: 'Match History', icon: 'pi pi-trophy', - routerLink: '/account/history', + routerLink: '/history', class: 'p-submenu-list', }, {