This commit is contained in:
Piotr K committed 2023-05-27 08:51:06 +02:00
1 parent 368cf265c5
commit d035328d84
32 files changed
+427 -44

No files matched your search

@@ -31,6 +31,13 @@ const routes: Routes = [
(m) => m.SolutionFormModule (m) => m.SolutionFormModule
), ),
}, },
{
path: 'issue/:id/solution',
loadChildren: () =>
import('./modules/solution/solution.module').then(
(m) => m.SolutionModule
),
},
]; ];
@NgModule({ @NgModule({
+2 -1
View File
@@ -12,6 +12,7 @@ import { SidebarModule } from 'primeng/sidebar';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { IssueEffect } from './store/effect/issue.effect'; import { IssueEffect } from './store/effect/issue.effect';
import { SolutionEffect } from './store/effect/solution.effect';
import { issueReducer } from './store/reducer/issue.reducer'; import { issueReducer } from './store/reducer/issue.reducer';
@NgModule({ @NgModule({
declarations: [AppComponent], declarations: [AppComponent],
@@ -20,7 +21,7 @@ import { issueReducer } from './store/reducer/issue.reducer';
AppRoutingModule, AppRoutingModule,
FontAwesomeModule, FontAwesomeModule,
StoreModule.forRoot({ issueState: issueReducer }), StoreModule.forRoot({ issueState: issueReducer }),
EffectsModule.forRoot([IssueEffect]), EffectsModule.forRoot([IssueEffect, SolutionEffect]),
HttpClientModule, HttpClientModule,
StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: !isDevMode() }), StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: !isDevMode() }),
SidebarModule, SidebarModule,
@@ -1,7 +1,6 @@
import { CommentModel } from './comment.model'; import { CommentModel } from './comment.model';
export interface SolutionModel { export interface SolutionModel {
id?: string;
title: string; title: string;
description: string; description: string;
comments: CommentModel[]; comments: CommentModel[];
@@ -0,0 +1,35 @@
import { CommonModule } from '@angular/common';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { ButtonModule } from 'primeng/button';
import { CardModule } from 'primeng/card';
import { InputTextModule } from 'primeng/inputtext';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { CommentFormComponent } from './components/comment-form/comment-form.component';
import { CommentComponent } from './components/comment/comment.component';
import { CommentsComponent } from './components/comments/comments.component';
import { CommentService, commentTypeToken } from './services/comment.service';
@NgModule({
declarations: [CommentsComponent, CommentComponent, CommentFormComponent],
imports: [
CommonModule,
CardModule,
InputTextModule,
ButtonModule,
InputTextareaModule,
ReactiveFormsModule,
],
exports: [CommentsComponent],
providers: [CommentService],
})
export class CommentModule {
static forRoot(
commentType: 'solution' | 'issues'
): ModuleWithProviders<CommentModule> {
return {
ngModule: CommentModule,
providers: [{ provide: commentTypeToken, useValue: commentType }],
};
}
}
@@ -0,0 +1,40 @@
<div class="comment-form">
<form [formGroup]="formGroup">
<p-card>
<ng-template pTemplate="header">
<div class="comment-header">
<span class="p-float-label">
<input
pInputText
id="username"
type="text"
formControlName="username"
/>
<label htmlFor="username">Nazwa Użytkownika</label>
</span>
</div>
</ng-template>
<span class="p-float-label">
<textarea
id="content"
rows="5"
cols="40"
pInputTextarea
[autoResize]="true"
formControlName="content"
></textarea>
<label htmlFor="content">Komentarz</label>
</span>
<ng-template pTemplate="footer">
<div class="footer">
<button
pButton
type="button"
label="Dodaj"
(click)="submit()"
></button>
</div>
</ng-template>
</p-card>
</form>
</div>
@@ -0,0 +1,9 @@
.footer {
display: flex;
justify-content: flex-end;
}
.comment-header {
margin-right: 20px;
padding: 28px 0 0 28px;
}
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CommentFormComponent } from './comment-form.component';
describe('CommentFormComponent', () => {
let component: CommentFormComponent;
let fixture: ComponentFixture<CommentFormComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [CommentFormComponent]
});
fixture = TestBed.createComponent(CommentFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,25 @@
import { Component, Input } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { CommentService } from '../../services/comment.service';
@Component({
selector: 'app-comment-form',
templateUrl: './comment-form.component.html',
styleUrls: ['./comment-form.component.scss'],
})
export class CommentFormComponent {
@Input() issueId!: string;
constructor(private commentService: CommentService) {}
formGroup = new FormGroup({
content: new FormControl('', Validators.required),
username: new FormControl('', Validators.required),
});
submit() {
const form = this.formGroup.getRawValue();
this.commentService
.addComment(this.issueId, form.username || '', form.content || '')
.subscribe();
}
}
@@ -0,0 +1,13 @@
<div class="comment">
<p-card>
<ng-template pTemplate="header">
<p class="comment-header">{{ comment.userName }}</p>
</ng-template>
<p>{{ comment.content }}</p>
<ng-template pTemplate="footer">
<div class="date">
{{ comment.createdAt | date : "dd.MM.yyyy, HH:mm" }}
</div>
</ng-template>
</p-card>
</div>
@@ -0,0 +1,9 @@
.date {
display: flex;
justify-content: end;
}
.comment-header {
margin-right: 20px;
padding: 20px 0 0 20px;
}
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CommentComponent } from './comment.component';
describe('CommentComponent', () => {
let component: CommentComponent;
let fixture: ComponentFixture<CommentComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [CommentComponent]
});
fixture = TestBed.createComponent(CommentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,11 @@
import { Component, Input } from '@angular/core';
import { CommentModel } from 'src/app/models/comment.model';
@Component({
selector: 'app-comment',
templateUrl: './comment.component.html',
styleUrls: ['./comment.component.scss'],
})
export class CommentComponent {
@Input() comment!: CommentModel;
}
@@ -0,0 +1,8 @@
<h2>Komentarze</h2>
<div class="comments">
<app-comment
*ngFor="let comment of comments"
[comment]="comment"
></app-comment>
<app-comment-form [issueId]="issueId"></app-comment-form>
</div>
@@ -0,0 +1,6 @@
.comments {
margin: 30px 0;
display: flex;
flex-direction: column;
gap: 15px;
}
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CommentsComponent } from './comments.component';
describe('CommentsComponent', () => {
let component: CommentsComponent;
let fixture: ComponentFixture<CommentsComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [CommentsComponent]
});
fixture = TestBed.createComponent(CommentsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,12 @@
import { Component, Input } from '@angular/core';
import { CommentModel } from 'src/app/models/comment.model';
@Component({
selector: 'app-comments',
templateUrl: './comments.component.html',
styleUrls: ['./comments.component.scss'],
})
export class CommentsComponent {
@Input() comments!: CommentModel[];
@Input() issueId!: string;
}
@@ -0,0 +1,31 @@
import { HttpClient } from '@angular/common/http';
import { Inject, Injectable, InjectionToken } from '@angular/core';
import { Store } from '@ngrx/store';
import { environments } from 'src/environments/environments';
export const commentTypeToken = new InjectionToken<'issues' | 'solution'>(
'Comment Type'
);
@Injectable()
export class CommentService {
constructor(
private http: HttpClient,
private store: Store,
@Inject(commentTypeToken) private commentType: 'issues' | 'solution'
) {}
addComment(issueId: string, username: string, content: string) {
return this.http.post(
environments.apiUrl +
'/issues/' +
issueId +
(this.commentType === 'issues' ? '' : '/solution') +
'/comments',
{
username,
content,
}
);
}
}
@@ -2,7 +2,12 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { IssueComponent } from './issue.component'; import { IssueComponent } from './issue.component';
const routes: Routes = [{ path: ':id', component: IssueComponent }]; const routes: Routes = [
{
path: ':id',
component: IssueComponent,
},
];
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
@@ -21,25 +21,13 @@
<p-button <p-button
*ngIf="(issue$ | async)?.solution" *ngIf="(issue$ | async)?.solution"
label="Rozwiązanie" label="Rozwiązanie"
[routerLink]="['/', 'solution', (issue$ | async)?.solution?.id]" [routerLink]="['/', 'issue', (issue$ | async)?.id, 'solution']"
></p-button> ></p-button>
</div> </div>
</p-card> </p-card>
</div> <app-comments
<h2>Komentarze</h2> [comments]="(issue$ | async)?.comments || []"
<div class="comments"> [issueId]="(issue$ | async)?.id || ''"
<div class="comment" *ngFor="let comment of (issue$ | async)?.comments"> ></app-comments>
<p-card>
<ng-template pTemplate="header">
<p class="comment-header">{{ comment.userName }}</p>
</ng-template>
<p>{{ comment.content }}</p>
<ng-template pTemplate="footer">
<div class="date">
{{ comment.createdAt | date : "dd.MM.yyyy, HH:mm" }}
</div>
</ng-template>
</p-card>
</div>
</div> </div>
</div> </div>
@@ -12,13 +12,6 @@
} }
} }
.comments {
margin: 30px 0;
display: flex;
flex-direction: column;
gap: 15px;
}
.main .content { .main .content {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
@@ -30,21 +23,8 @@
justify-content: space-around; justify-content: space-around;
align-items: flex-start; align-items: flex-start;
} }
.date {
display: flex;
justify-content: end;
}
.comment-header {
margin-right: 20px;
padding: 20px 0 0 20px;
}
.issue { .issue {
max-width: 900px; max-width: 900px;
margin: auto; margin: auto;
} }
::ng-deep .p-card-content {
padding: 0 !important;
}
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { ButtonModule } from 'primeng/button'; import { ButtonModule } from 'primeng/button';
import { CardModule } from 'primeng/card'; import { CardModule } from 'primeng/card';
import { TagModule } from 'primeng/tag'; import { TagModule } from 'primeng/tag';
import { CommentModule } from '../comment/comment.module';
import { IssueRoutingModule } from './issue-routing.module'; import { IssueRoutingModule } from './issue-routing.module';
import { IssueComponent } from './issue.component'; import { IssueComponent } from './issue.component';
@@ -14,6 +15,7 @@ import { IssueComponent } from './issue.component';
IssueRoutingModule, IssueRoutingModule,
TagModule, TagModule,
ButtonModule, ButtonModule,
CommentModule.forRoot('issues'),
], ],
providers: [], providers: [],
}) })
@@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { tap } from 'rxjs'; import { tap } from 'rxjs';
import { IssueModel } from 'src/app/models/issue.model'; import { IssueModel } from 'src/app/models/issue.model';
import { receivedSolutionSuccessAction } from 'src/app/store/actions/solution.actions';
import { environments } from 'src/environments/environments'; import { environments } from 'src/environments/environments';
@Injectable({ @Injectable({
@@ -16,6 +17,20 @@ export class SolutionFormService {
title, title,
description, description,
}) })
.pipe(tap()); .pipe(
tap(() =>
this.store.dispatch(
receivedSolutionSuccessAction({
issueId: issue.id,
solution: {
comments: [],
createdAt: new Date(),
description,
title,
},
})
)
)
);
} }
} }
@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SolutionComponent } from './solution.component';
const routes: Routes = [{ path: '', component: SolutionComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class SolutionRoutingModule {}
@@ -0,0 +1,18 @@
<div class="solution">
<div class="main">
<p-card>
<ng-template pTemplate="header">
<div class="header">
<h1>{{ (solution$ | async)?.title }}</h1>
</div>
</ng-template>
<div class="content">
<p>{{ (solution$ | async)?.description }}</p>
</div>
</p-card>
<app-comments
[comments]="(solution$ | async)?.comments || []"
[issueId]="issueId"
></app-comments>
</div>
</div>
@@ -0,0 +1,25 @@
.header {
margin: 10px 30px;
display: flex;
align-items: center;
h1 {
flex: 7;
}
}
.main .content {
display: flex;
justify-content: space-around;
align-items: center;
}
.comments .content {
display: flex;
justify-content: space-around;
align-items: flex-start;
}
.solution {
max-width: 900px;
margin: auto;
}
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SolutionComponent } from './solution.component';
describe('SolutionComponent', () => {
let component: SolutionComponent;
let fixture: ComponentFixture<SolutionComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SolutionComponent]
});
fixture = TestBed.createComponent(SolutionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,24 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store';
import { EMPTY, Observable } from 'rxjs';
import { SolutionModel } from 'src/app/models/solution.model';
import { selectSolution } from 'src/app/store/selectors/solution.selectors';
@Component({
selector: 'app-solution',
templateUrl: './solution.component.html',
styleUrls: ['./solution.component.scss'],
})
export class SolutionComponent implements OnInit {
solution$: Observable<SolutionModel | undefined> = EMPTY;
issueId!: string;
constructor(private store: Store, private route: ActivatedRoute) {}
ngOnInit(): void {
this.route.params.subscribe((params) => {
this.issueId = params['id'];
this.solution$ = this.store.select(selectSolution(params['id']));
});
}
}
@@ -0,0 +1,17 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { CardModule } from 'primeng/card';
import { CommentModule } from '../comment/comment.module';
import { SolutionRoutingModule } from './solution-routing.module';
import { SolutionComponent } from './solution.component';
@NgModule({
imports: [
CommonModule,
CommentModule.forRoot('solution'),
SolutionRoutingModule,
CardModule,
],
declarations: [SolutionComponent],
})
export class SolutionModule {}
@@ -1,4 +1,3 @@
<div *ngFor="let solution of solutions$ | async"> <div *ngFor="let solution of solutions$ | async">
{{solution.title}} {{ solution.title }}
</div> </div>
gv gvghv
@@ -0,0 +1,5 @@
import { createSelector } from '@ngrx/store';
import { selectIssue } from './issue.selectors';
export const selectSolution = (issueId: string) =>
createSelector(selectIssue(issueId), (issue) => issue?.solution);
@@ -1,4 +1,4 @@
// Dev // Dev
export const environments = { export const environments = {
apiUrl: 'http://localhost', apiUrl: 'http://localhost/api',
}; };
+4
View File
@@ -16,3 +16,7 @@ h3,
p { p {
color: #495057; color: #495057;
} }
.p-card-content {
padding: 0 !important;
}