diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1a29c63..9637082 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -208,6 +208,7 @@ import { CircleParallelMinorPipePipe } from './tools/pipes/circle-parallel-minor import { MitgliedInstrumenteListComponent } from './components/mitglieder/mitglied-instrumente-list/mitglied-instrumente-list.component'; import { MkjRatingComponent } from './utilities/form-input-components/mkj-rating/mkj-rating.component'; import { MkjUserNotificationsComponent } from './utilities/mkj-user-notifications/mkj-user-notifications.component'; +import { MkjCommentsComponent } from './utilities/mkj-comments/mkj-comments.component'; // FullCalendarModule.registerPlugins([ // dayGridPlugin, @@ -416,6 +417,7 @@ registerLocaleData(localeDe); MitgliedInstrumenteListComponent, MkjRatingComponent, MkjUserNotificationsComponent, + MkjCommentsComponent, ], providers: [ mkjAppInitializer(), diff --git a/src/app/components/archiv/noten/noten-overview/noten-overview.component.html b/src/app/components/archiv/noten/noten-overview/noten-overview.component.html index f417ae7..bbe6d5a 100644 --- a/src/app/components/archiv/noten/noten-overview/noten-overview.component.html +++ b/src/app/components/archiv/noten/noten-overview/noten-overview.component.html @@ -23,6 +23,10 @@ (click)="navigateToEditView(noten)" > + +
+ +
diff --git a/src/app/models/Kommentar.ts b/src/app/models/Kommentar.ts new file mode 100644 index 0000000..d44fc9d --- /dev/null +++ b/src/app/models/Kommentar.ts @@ -0,0 +1,15 @@ +import { ModelType } from './_ModelType'; + +export interface Kommentar { + id?: string; + text?: string; + commentable_type?: ModelType; + commentable_id?: string; + mitglied_id?: number; + mitglied_name?: string; + number_child_comments?: number; + parent_comment_id?: string; + created_at?: string; + updated_at?: string; + subComments?: Kommentar[]; +} diff --git a/src/app/models/_ModelType.ts b/src/app/models/_ModelType.ts new file mode 100644 index 0000000..86a1fcb --- /dev/null +++ b/src/app/models/_ModelType.ts @@ -0,0 +1,5 @@ +export enum ModelType { + Kommentar = 'kommentar', + Mitglied = 'mitglied', + Noten = 'noten', +} diff --git a/src/app/services/api/_generic-field-value-serivce.interface.ts b/src/app/services/api/_generic-field-value-serivce.interface.ts index 6af624c..02c1b41 100644 --- a/src/app/services/api/_generic-field-value-serivce.interface.ts +++ b/src/app/services/api/_generic-field-value-serivce.interface.ts @@ -1,6 +1,5 @@ import { Observable } from 'rxjs'; import { GetListOutput } from 'src/app/interfaces/api-middleware'; -import { KeyOf } from 'src/app/types/KeyOf'; import { GenericFieldValue } from 'src/app/utilities/_list-datasources/generic-field-values-datasource.class'; export interface GenericFieldValueService { diff --git a/src/app/services/api/kommentar-api.service.ts b/src/app/services/api/kommentar-api.service.ts new file mode 100644 index 0000000..1d9ddd3 --- /dev/null +++ b/src/app/services/api/kommentar-api.service.ts @@ -0,0 +1,15 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Kommentar } from 'src/app/models/Kommentar'; +import { AbstractCrudApiService } from './_abstract-crud-api-service.class'; + +@Injectable({ + providedIn: 'root', +}) +export class KommentarApiService extends AbstractCrudApiService { + protected controllerApiUrlKey = 'kommentare'; + + constructor(httpClient: HttpClient) { + super(httpClient); + } +} diff --git a/src/app/services/api/noten-api.service.ts b/src/app/services/api/noten-api.service.ts index f851eb2..a2c7e41 100644 --- a/src/app/services/api/noten-api.service.ts +++ b/src/app/services/api/noten-api.service.ts @@ -6,11 +6,12 @@ import { environment } from 'src/environments/environment'; import { Noten } from '../../models/Noten'; import { AbstractCrudApiService } from './_abstract-crud-api-service.class'; import { KeyOf } from 'src/app/types/KeyOf'; +import { GenericFieldValueService } from './_generic-field-value-serivce.interface'; @Injectable({ providedIn: 'root', }) -export class NotenApiService extends AbstractCrudApiService { +export class NotenApiService extends AbstractCrudApiService implements GenericFieldValueService { protected controllerApiUrlKey: string = 'noten'; private apiURL = environment.apiUrl; diff --git a/src/app/utilities/_display-model-configurations/display-model-configuration.interface.ts b/src/app/utilities/_display-model-configurations/display-model-configuration.interface.ts index 41898c2..1230809 100644 --- a/src/app/utilities/_display-model-configurations/display-model-configuration.interface.ts +++ b/src/app/utilities/_display-model-configurations/display-model-configuration.interface.ts @@ -1,6 +1,7 @@ export interface DisplayModelConfiguration { fields: DisplayModelField[]; rateable?: boolean; + commentable?: boolean; } export interface DisplayModelField { diff --git a/src/app/utilities/mkj-comments/mkj-comments.component.html b/src/app/utilities/mkj-comments/mkj-comments.component.html new file mode 100644 index 0000000..66808c9 --- /dev/null +++ b/src/app/utilities/mkj-comments/mkj-comments.component.html @@ -0,0 +1,81 @@ +@for (c of comments; track c) { + +} +@if (subCommentId == null) { +
+ +
+} + + +
+
+
+ + {{ comment.mitglied_name }} + +
+ + {{ comment.updated_at | date: 'E d. MMM yyyy | HH:mm' }} + +
+
{{ comment.text }}
+ + @if (subCommentId === comment.id) { +
+ +
+ } + @if (comment.subComments?.length > 0) { +
+ @for (c of comment.subComments; track c) { + + } +
+ } +
+
+ + +
+ + +
+
diff --git a/src/app/utilities/mkj-comments/mkj-comments.component.scss b/src/app/utilities/mkj-comments/mkj-comments.component.scss new file mode 100644 index 0000000..535ba46 --- /dev/null +++ b/src/app/utilities/mkj-comments/mkj-comments.component.scss @@ -0,0 +1,53 @@ +.comment { + display: flex; + flex-direction: column; + + border: 1px solid var(--surface-200); + border-radius: 5px; + + &__header { + display: flex; + gap: 0.5rem; + align-items: end; + justify-content: space-between; + padding: 10px; + font-weight: 600; + background: var(--surface-200); + + .date { + font-weight: normal; + opacity: 0.7; + } + } + + &__text { + padding: 10px; + } + + &__footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + width: 100%; + } + + &__subcomments { + display: flex; + flex-direction: column; + gap: 1rem; + margin-left: 3rem; + padding: 10px; + } + + &__input_wrapper { + padding: 10px; + background: var(--surface-200); + } + + &__input { + display: flex; + align-items: center; + justify-content: end; + } +} diff --git a/src/app/utilities/mkj-comments/mkj-comments.component.ts b/src/app/utilities/mkj-comments/mkj-comments.component.ts new file mode 100644 index 0000000..aa91055 --- /dev/null +++ b/src/app/utilities/mkj-comments/mkj-comments.component.ts @@ -0,0 +1,114 @@ +import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { GetListInput } from 'src/app/interfaces/api-middleware'; +import { Kommentar } from 'src/app/models/Kommentar'; +import { ModelType } from 'src/app/models/_ModelType'; +import { KommentarApiService } from 'src/app/services/api/kommentar-api.service'; + +@Component({ + selector: 'mkj-comments', + templateUrl: './mkj-comments.component.html', + styleUrl: './mkj-comments.component.scss', +}) +export class MkjCommentsComponent implements OnInit { + @Input({ required: true }) modelType: ModelType; + @Input({ required: true }) modelId: string; + + @ViewChild('inputTemplate') inputTemplate: any; + + public comments: Kommentar[] = []; + public commentText: string; + public subCommentId: string; + + public saving = false; + + constructor(private apiService: KommentarApiService) {} + + public ngOnInit(): void { + this.getComments(); + } + + public getComments(parent?: Kommentar): void { + if (parent) { + (parent as any).loading = true; + } + + const input: GetListInput = { + filterAnd: [ + { + field: 'commentable_type', + value: this.modelType, + }, + { + field: 'commentable_id', + value: this.modelId, + }, + { + field: 'parent_comment_id', + value: parent?.id || null, + operator: '=', + }, + ], + sort: { + field: 'updated_at', + order: 'asc', + }, + }; + + this.apiService.getList(input).subscribe((response) => { + if (parent) { + parent.subComments = response.values; + (parent as any).loading = false; + } else { + this.comments = response.values; + } + }); + } + + public createComment(parent?: Kommentar): void { + this.saving = true; + + this.apiService + .create({ + text: this.commentText, + commentable_type: this.modelType, + commentable_id: this.modelId, + parent_comment_id: parent?.id, + }) + .subscribe((response) => { + this.insertComment(response); + this.saving = false; + }); + } + + public initCommentInput(comment: Kommentar, text?: string): void { + this.subCommentId = this.subCommentId === comment.id ? null : comment.id; + this.commentText = text || ''; + } + + private insertComment(comment: Kommentar): void { + this.commentText = ''; + this.subCommentId = null; + if (comment.parent_comment_id) { + const parent = this.findParentComment(comment, this.comments); + if (parent) { + parent.subComments = [...(parent.subComments ?? []), comment]; + parent.number_child_comments++; + } + } else { + this.comments.push(comment); + } + } + + private findParentComment(c: Kommentar, arr: Kommentar[]): Kommentar { + for (const item of arr) { + if (item.id === c.parent_comment_id) { + return item; + } else if (item.subComments) { + const result = this.findParentComment(c, item.subComments); + if (result) return result; + } + } + + return null; + } +} diff --git a/src/app/utilities/mkj-user-notifications/mkj-user-notifications.component.html b/src/app/utilities/mkj-user-notifications/mkj-user-notifications.component.html index a15a8a6..2f128c3 100644 --- a/src/app/utilities/mkj-user-notifications/mkj-user-notifications.component.html +++ b/src/app/utilities/mkj-user-notifications/mkj-user-notifications.component.html @@ -1,6 +1,7 @@