diff --git a/package-lock.json b/package-lock.json index c577984..425937b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@ngx-translate/core": "^15.0.0", "@ngx-translate/http-loader": "^8.0.0", "@popperjs/core": "^2.11.6", + "@types/youtube": "^0.0.50", "aos": "^2.3.4", "bootstrap": "^5.3.2", "crypto-js": "^4.2.0", @@ -6417,6 +6418,11 @@ "@types/node": "*" } }, + "node_modules/@types/youtube": { + "version": "0.0.50", + "resolved": "https://registry.npmjs.org/@types/youtube/-/youtube-0.0.50.tgz", + "integrity": "sha512-d4GpH4uPYp9W07kc487tiq6V/EUHl18vZWFMbQoe4Sk9LXEWzFi/BMf9x7TI4m7/j7gU3KeX8H6M8aPBgykeLw==" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b05fa96..bfc5320 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -40,6 +40,9 @@ import { MatButtonModule } from '@angular/material/button'; import { ConfirmDialogComponent } from './components/general/dialog/confirm-dialog/confirm-dialog.component'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { QuestionEditDialogComponent } from './components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatChipsModule } from '@angular/material/chips'; // AOT compilation support export function HttpLoaderFactory(http: HttpClient) { @@ -72,8 +75,11 @@ export function HttpLoaderFactory(http: HttpClient) { CelebrationCardDialogComponent, ConfettiComponent, FileUploadComponent, - ConfirmDialogComponent + ConfirmDialogComponent, + QuestionEditDialogComponent ], + // ... + imports: [ BrowserModule, AppRoutingModule, @@ -96,7 +102,10 @@ export function HttpLoaderFactory(http: HttpClient) { MatInputModule, MatButtonModule, MatSnackBarModule, - MatProgressBarModule + MatFormFieldModule, + MatChipsModule, // Add this line + MatProgressBarModule, + MatChipsModule // Add this line ], providers: [], bootstrap: [AppComponent] diff --git a/src/app/components/admin/admin-routing.module.ts b/src/app/components/admin/admin-routing.module.ts index 80c8b38..bdf5484 100644 --- a/src/app/components/admin/admin-routing.module.ts +++ b/src/app/components/admin/admin-routing.module.ts @@ -8,6 +8,7 @@ import { VisitorComponent } from './visitor/visitor.component'; import { ChatComponent } from './chat/chat.component'; import { MarkdownRendererComponent } from './markdown-renderer/markdown-renderer.component'; import { FileUploadComponent } from '../general/file/file-upload/file-upload.component'; +import { CsharpInterviewQaComponent } from './topic/csharp-interview-qa/csharp-interview-qa.component'; const routes: Routes = [{ path: "", component: AdminComponent, children: [ @@ -17,7 +18,8 @@ const routes: Routes = [{ { path: "visitor", component: VisitorComponent }, { path: "chat", component: ChatComponent }, { path: "markdown-renderer", component: MarkdownRendererComponent }, - { path: "file", component: FileUploadComponent } + { path: "file", component: FileUploadComponent }, + { path: "topic", component: CsharpInterviewQaComponent } ] }]; diff --git a/src/app/components/admin/admin.module.ts b/src/app/components/admin/admin.module.ts index d5bc52f..d16b4b3 100644 --- a/src/app/components/admin/admin.module.ts +++ b/src/app/components/admin/admin.module.ts @@ -37,6 +37,7 @@ import { ConfirmDialogComponent } from './dialog/confirm-dialog/confirm-dialog.c import { VisitorComponent } from './visitor/visitor.component'; import { ChatComponent } from './chat/chat.component'; import { MarkdownRendererComponent } from './markdown-renderer/markdown-renderer.component'; +import { CsharpInterviewQaComponent } from './topic/csharp-interview-qa/csharp-interview-qa.component'; @NgModule({ declarations: [ @@ -56,6 +57,7 @@ import { MarkdownRendererComponent } from './markdown-renderer/markdown-renderer VisitorComponent, ChatComponent, MarkdownRendererComponent, + CsharpInterviewQaComponent, ], imports: [ CommonModule, diff --git a/src/app/components/admin/navigation/side-nav/side-nav.component.html b/src/app/components/admin/navigation/side-nav/side-nav.component.html index 4f4bce0..855199d 100644 --- a/src/app/components/admin/navigation/side-nav/side-nav.component.html +++ b/src/app/components/admin/navigation/side-nav/side-nav.component.html @@ -1,5 +1,5 @@ - + diff --git a/src/app/components/admin/topic/csharp-interview-qa/csharp-interview-qa.component.css b/src/app/components/admin/topic/csharp-interview-qa/csharp-interview-qa.component.css new file mode 100644 index 0000000..386e43a --- /dev/null +++ b/src/app/components/admin/topic/csharp-interview-qa/csharp-interview-qa.component.css @@ -0,0 +1,27 @@ +.question-list { + float: left; + width: 50%; +} + +.question-details { + float: right; + width: 50%; +} + +ul { + list-style-type: none; + padding: 0; +} + +li { + cursor: pointer; + padding: 8px; + border: 1px solid #ddd; + margin-top: -1px; /* Prevent double borders */ + background-color: #f6f6f6; + text-align: left; +} + +li:hover { + background-color: #ddd; +} diff --git a/src/app/components/admin/topic/csharp-interview-qa/csharp-interview-qa.component.html b/src/app/components/admin/topic/csharp-interview-qa/csharp-interview-qa.component.html new file mode 100644 index 0000000..5a49352 --- /dev/null +++ b/src/app/components/admin/topic/csharp-interview-qa/csharp-interview-qa.component.html @@ -0,0 +1,18 @@ + +
+

Questions

+
    +
  • + {{ question.questionText }} +
  • +
+
+
+

{{ selectedQuestion.questionText }}

+
+ + + + +
diff --git a/src/app/components/admin/topic/csharp-interview-qa/csharp-interview-qa.component.spec.ts b/src/app/components/admin/topic/csharp-interview-qa/csharp-interview-qa.component.spec.ts new file mode 100644 index 0000000..9d6554d --- /dev/null +++ b/src/app/components/admin/topic/csharp-interview-qa/csharp-interview-qa.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CsharpInterviewQaComponent } from './csharp-interview-qa.component'; + +describe('CsharpInterviewQaComponent', () => { + let component: CsharpInterviewQaComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [CsharpInterviewQaComponent] + }); + fixture = TestBed.createComponent(CsharpInterviewQaComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/admin/topic/csharp-interview-qa/csharp-interview-qa.component.ts b/src/app/components/admin/topic/csharp-interview-qa/csharp-interview-qa.component.ts new file mode 100644 index 0000000..72b8411 --- /dev/null +++ b/src/app/components/admin/topic/csharp-interview-qa/csharp-interview-qa.component.ts @@ -0,0 +1,90 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { QuestionEditDialogComponent } from 'src/app/components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component'; +import { Question } from 'src/app/models/topic/question'; +import { QaDataService } from 'src/app/services/general/qa-data/qa-data.service'; + +@Component({ + selector: 'app-csharp-interview-qa', + templateUrl: './csharp-interview-qa.component.html', + styleUrls: ['./csharp-interview-qa.component.css'] +}) +export class CsharpInterviewQaComponent implements + + OnInit { + questions: Question[] = []; + selectedQuestion: Question | null = null; + + constructor(private questionService: QaDataService, + public dialog: MatDialog, private snackBar: MatSnackBar) { } + + ngOnInit(): void { + this.loadQuestions(); + } + + loadQuestions(): void { + this.questionService.getQuestions().subscribe(data => { + this.questions = data; + }); + } + + selectQuestion(question: Question): void { + this.selectedQuestion = question; + } + + createQuestion(question: Question): void { + this.questionService.createQuestion(question).subscribe(() => { + this.loadQuestions(); + }); + } + + updateQuestion(question: Question): void { + if (question.id) { + this.questionService.updateQuestion(question).subscribe(() => { + this.loadQuestions(); + }); + } + } + + deleteQuestion(id: string): void { + this.questionService.deleteQuestion(id).subscribe(() => { + this.loadQuestions(); + }); + } + + handleKeyUp(event: KeyboardEvent): void { + // Handle key up event here + } + + openEditDialog(question?: Question): void { + // Create copies of tags and references as comma-separated strings + const editedQuestion = { + ...question, + tags: question?.tags.join(', '), + references: question?.references.join(', ') + }; + + + const dialogRef = this.dialog.open(QuestionEditDialogComponent, { + width: '800px', // Set the desired width for the dialog + data: editedQuestion // Pass the edited question to the dialog + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + // Check if the result has an id to determine if it's a new or existing question + if (result.id) { + console.log("result.id: " + result.id); + // Split tags and references back into arrays before updating + result.tags = result.tags.split(',').map((tag: string) => tag.trim()).filter((tag: string) => tag); + result.references = result.references.split(',').map((ref: string) => ref.trim()).filter((ref: string) => ref); + this.updateQuestion(result); // Update the question with the result from the dialog + } + else { + this.createQuestion(result); + } + } + }); + } +} diff --git a/src/app/components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component.css b/src/app/components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component.html b/src/app/components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component.html new file mode 100644 index 0000000..08b7309 --- /dev/null +++ b/src/app/components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component.html @@ -0,0 +1,31 @@ +

Edit Question

+
+ + Topic + + + + Subtopic + + + + Difficulty + + + + Tags + + + + References + + + + Answer + + +
+
+ + +
diff --git a/src/app/components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component.spec.ts b/src/app/components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component.spec.ts new file mode 100644 index 0000000..2bc9cb0 --- /dev/null +++ b/src/app/components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { QuestionEditDialogComponent } from './question-edit-dialog.component'; + +describe('QuestionEditDialogComponent', () => { + let component: QuestionEditDialogComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [QuestionEditDialogComponent] + }); + fixture = TestBed.createComponent(QuestionEditDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component.ts b/src/app/components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component.ts new file mode 100644 index 0000000..a486d24 --- /dev/null +++ b/src/app/components/general/dialog/topic/question-edit-dialog/question-edit-dialog.component.ts @@ -0,0 +1,69 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { MatChipInputEvent } from '@angular/material/chips'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Question } from 'src/app/models/topic/question'; +import { QaDataService } from 'src/app/services/general/qa-data/qa-data.service'; +import { ENTER, COMMA } from '@angular/cdk/keycodes'; + +@Component({ + selector: 'app-question-edit-dialog', + templateUrl: './question-edit-dialog.component.html', + styleUrls: ['./question-edit-dialog.component.css'] +}) +export class QuestionEditDialogComponent implements OnInit { + readonly separatorKeysCodes = [ENTER, COMMA] as const; + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: Question, private questionService: QaDataService) { } + + ngOnInit() { + } + + onCancelClick(): void { + this.dialogRef.close(); + } + + onSaveClick(): void { + // Add the logic to save the question + console.log(this.data); + // this.questionService.updateQuestion(this.data.questionId ?? '', this.data).subscribe(); + // Then close the dialog + this.dialogRef.close(this.data); + } + + addTag(event: MatChipInputEvent): void { + const input = event.input; + const value = (event.value || '').trim(); + + if (value) { + this.data.tags.push(value); + } + + if (input) { + input.value = ''; + } + } + + removeTag(tag: string): void { + const index = this.data.tags.indexOf(tag); + if (index >= 0) { + this.data.tags.splice(index, 1); + } + } + + addReference(event: MatChipInputEvent): void { + const value = (event.value || '').trim(); + if (value) { + this.data.references.push(value); + } + // Clear the input value + event.chipInput!.clear(); + } + + removeReference(ref: string): void { + const index = this.data.references.indexOf(ref); + if (index >= 0) { + this.data.references.splice(index, 1); + } + } +} diff --git a/src/app/components/home/video/video.component.html b/src/app/components/home/video/video.component.html index 184e2c7..20b7094 100644 --- a/src/app/components/home/video/video.component.html +++ b/src/app/components/home/video/video.component.html @@ -4,5 +4,10 @@
- +
diff --git a/src/app/models/admin/navbar/menu.ts b/src/app/models/admin/navbar/menu.ts index 542307f..2d08470 100644 --- a/src/app/models/admin/navbar/menu.ts +++ b/src/app/models/admin/navbar/menu.ts @@ -63,5 +63,10 @@ export const menu: NavItem[] = [ displayName: 'Files', iconName: 'cloud_upload', route: 'file' + }, + { + displayName: 'Topic', + iconName: 'question_answer', + route: 'topic' } ]; diff --git a/src/app/models/topic/question.ts b/src/app/models/topic/question.ts new file mode 100644 index 0000000..1ec173c --- /dev/null +++ b/src/app/models/topic/question.ts @@ -0,0 +1,11 @@ +export interface Question { + id?: string; + questionId?: string; + topic: string; + subtopic: string; + questionText: string; + answer: string; + difficulty: string; + tags: string[]; + references: string[]; +} diff --git a/src/app/services/general/qa-data/qa-data.service.spec.ts b/src/app/services/general/qa-data/qa-data.service.spec.ts new file mode 100644 index 0000000..8137566 --- /dev/null +++ b/src/app/services/general/qa-data/qa-data.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { QaDataService } from './qa-data.service'; + +describe('QaDataService', () => { + let service: QaDataService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(QaDataService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/general/qa-data/qa-data.service.ts b/src/app/services/general/qa-data/qa-data.service.ts new file mode 100644 index 0000000..57dfe92 --- /dev/null +++ b/src/app/services/general/qa-data/qa-data.service.ts @@ -0,0 +1,51 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { Observable } from 'rxjs'; +import { Question } from 'src/app/models/topic/question'; +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root' +}) +export class QaDataService { + + private apiUrl = `${environment.awsUserApiBaseUrl}` + '/api/questions'; + private result = Observable.create(); + + constructor(private http: HttpClient, private snackBar: MatSnackBar) { } + + getQuestions(): Observable { + return this.http.get(this.apiUrl); + } + + getQuestion(id: string): Observable { + return this.http.get(`${this.apiUrl}/'single'/${id}`); + } + + createQuestion(question: Question): Observable { + return this.http.post(this.apiUrl, question); + } + + updateQuestion(question: Question): Observable { + const url = `${this.apiUrl}/${question.questionId}`; + console.log(question.questionId); + console.log(url) + + this.http.put(url, question).subscribe(result => { + // Display a success message + this.snackBar.open('Question updated successfully!', 'Close', { + duration: 10000, + horizontalPosition: 'center', + verticalPosition: 'bottom', + }); + + this.result = result; + }); + return this.result; + } + + deleteQuestion(id: string): Observable { + return this.http.delete(`${this.apiUrl}/${id}`); + } +}