diff --git a/apps/backend/src/issues/dto/create-issue.dto.ts b/apps/backend/src/issues/dto/create-issue.dto.ts index 09fff86..3d5b076 100644 --- a/apps/backend/src/issues/dto/create-issue.dto.ts +++ b/apps/backend/src/issues/dto/create-issue.dto.ts @@ -32,5 +32,5 @@ export class CreateIssueDto { latitude: number; @IsNumber() - longitude: string; + longitude: number; } diff --git a/apps/frontend/angular.json b/apps/frontend/angular.json index 308f72b..895327a 100644 --- a/apps/frontend/angular.json +++ b/apps/frontend/angular.json @@ -33,6 +33,12 @@ }, "configurations": { "production": { + "fileReplacements": [ + { + "replace": "src/environments/environments.ts", + "with": "src/environments/environments.prod.ts" + } + ], "budgets": [ { "type": "initial", diff --git a/apps/frontend/src/app/app-routing.module.ts b/apps/frontend/src/app/app-routing.module.ts index 8238442..b7071be 100644 --- a/apps/frontend/src/app/app-routing.module.ts +++ b/apps/frontend/src/app/app-routing.module.ts @@ -24,6 +24,13 @@ const routes: Routes = [ loadChildren: () => import('./modules/report/report.module').then((m) => m.ReportModule), }, + { + path: 'solution-form', + loadChildren: () => + import('./modules/solution-form/solution-form.module').then( + (m) => m.SolutionFormModule + ), + }, ]; @NgModule({ diff --git a/apps/frontend/src/app/app.component.html b/apps/frontend/src/app/app.component.html index 6099999..891d9ec 100644 --- a/apps/frontend/src/app/app.component.html +++ b/apps/frontend/src/app/app.component.html @@ -31,5 +31,17 @@ label="Rozwiązania" [routerLink]="['/', 'solutions']" > + + diff --git a/apps/frontend/src/app/models/solution.model.ts b/apps/frontend/src/app/models/solution.model.ts index cbcaa69..304cd2c 100644 --- a/apps/frontend/src/app/models/solution.model.ts +++ b/apps/frontend/src/app/models/solution.model.ts @@ -1,6 +1,7 @@ import { CommentModel } from './comment.model'; export interface SolutionModel { + id?: string; title: string; description: string; comments: CommentModel[]; diff --git a/apps/frontend/src/app/modules/report/report.component.html b/apps/frontend/src/app/modules/report/report.component.html index ca7ebc9..c46b303 100644 --- a/apps/frontend/src/app/modules/report/report.component.html +++ b/apps/frontend/src/app/modules/report/report.component.html @@ -39,7 +39,7 @@ - + diff --git a/apps/frontend/src/app/modules/report/report.component.ts b/apps/frontend/src/app/modules/report/report.component.ts index 8fe75ea..ff6cae5 100644 --- a/apps/frontend/src/app/modules/report/report.component.ts +++ b/apps/frontend/src/app/modules/report/report.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { ReportService } from './services/report.service'; @Component({ selector: 'app-report', @@ -7,9 +8,13 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; styleUrls: ['./report.component.scss'], }) export class ReportComponent { + constructor(private reportService: ReportService) {} formGroup = new FormGroup({ userName: new FormControl('', Validators.required), - cords: new FormControl(undefined, Validators.required), + cords: new FormControl( + undefined as unknown as [number, number], + Validators.required + ), title: new FormControl('', Validators.required), type: new FormControl(undefined, Validators.required), description: new FormControl('', Validators.required), @@ -19,4 +24,18 @@ export class ReportComponent { { value: 'problem', label: 'Problem' }, { value: 'suggestion', label: 'Sugestia' }, ]; + submit() { + const form = this.formGroup.getRawValue(); + if (form.cords) + this.reportService + .createIssue( + form.title || '', + form.userName || '', + form.description || '', + form.type || '', + form.cords[0] || 0, + form.cords[1] || 0 + ) + .subscribe(console.log); + } } diff --git a/apps/frontend/src/app/modules/report/services/report.service.ts b/apps/frontend/src/app/modules/report/services/report.service.ts index 924f80d..fcc8267 100644 --- a/apps/frontend/src/app/modules/report/services/report.service.ts +++ b/apps/frontend/src/app/modules/report/services/report.service.ts @@ -1,6 +1,50 @@ +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { tap } from 'rxjs'; +import { addIssueAction } from 'src/app/store/actions/issue.actions'; +import { environments } from 'src/environments/environments'; @Injectable({ providedIn: 'root', }) -export class ReportService {} +export class ReportService { + constructor(private http: HttpClient, private store: Store) {} + createIssue( + title: string, + username: string, + desc: string, + type: string, + latitude: number, + longitude: number + ) { + return this.http + .post(environments.apiUrl + '/issues', { + title, + username, + description: desc, + type, + longitude, + latitude, + }) + .pipe( + tap((res: any) => { + this.store.dispatch( + addIssueAction({ + issue: { + comments: [], + cords: [longitude, latitude], + createdAt: new Date(), + description: desc, + id: res.id, + photos: [], + status: 'new', + title, + type: type as 'problem' | 'suggestion', + }, + }) + ); + }) + ); + } +} diff --git a/apps/frontend/src/app/modules/solution-form/services/solution-form.service.ts b/apps/frontend/src/app/modules/solution-form/services/solution-form.service.ts new file mode 100644 index 0000000..29e0c0e --- /dev/null +++ b/apps/frontend/src/app/modules/solution-form/services/solution-form.service.ts @@ -0,0 +1,21 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { tap } from 'rxjs'; +import { IssueModel } from 'src/app/models/issue.model'; +import { environments } from 'src/environments/environments'; + +@Injectable({ + providedIn: 'root', +}) +export class SolutionFormService { + constructor(private http: HttpClient, private store: Store) {} + createSolution(issue: IssueModel, title: string, description: string) { + return this.http + .post(environments.apiUrl + '/issues/' + issue.id + '/solution', { + title, + description, + }) + .pipe(tap()); + } +} diff --git a/apps/frontend/src/app/modules/solution-form/solution-form-routiong.module.ts b/apps/frontend/src/app/modules/solution-form/solution-form-routiong.module.ts new file mode 100644 index 0000000..66dfaea --- /dev/null +++ b/apps/frontend/src/app/modules/solution-form/solution-form-routiong.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { SolutionFormComponent } from './solution-form.component'; + +const routes: Routes = [{ path: '', component: SolutionFormComponent }]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class SolutionFormRoutingModule {} diff --git a/apps/frontend/src/app/modules/solution-form/solution-form.component.html b/apps/frontend/src/app/modules/solution-form/solution-form.component.html new file mode 100644 index 0000000..7ff73e7 --- /dev/null +++ b/apps/frontend/src/app/modules/solution-form/solution-form.component.html @@ -0,0 +1,32 @@ +
+ +
+ + + + + + + + + + + + + +
+
+
diff --git a/apps/frontend/src/app/modules/solution-form/solution-form.component.scss b/apps/frontend/src/app/modules/solution-form/solution-form.component.scss new file mode 100644 index 0000000..9c23e58 --- /dev/null +++ b/apps/frontend/src/app/modules/solution-form/solution-form.component.scss @@ -0,0 +1,11 @@ +.report { + max-width: 900px; + margin: auto; +} + +form { + display: flex; + align-items: center; + flex-direction: column; + gap: 30px; +} diff --git a/apps/frontend/src/app/modules/solution-form/solution-form.component.ts b/apps/frontend/src/app/modules/solution-form/solution-form.component.ts new file mode 100644 index 0000000..700a2fd --- /dev/null +++ b/apps/frontend/src/app/modules/solution-form/solution-form.component.ts @@ -0,0 +1,57 @@ +import { Component, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { EMPTY, Observable, map, tap } from 'rxjs'; +import { IssueModel } from 'src/app/models/issue.model'; +import { receiveAllIssuesAction } from 'src/app/store/actions/issue.actions'; +import { selectAllIssues } from 'src/app/store/selectors/issue.selectors'; +import { SolutionFormService } from './services/solution-form.service'; + +@Component({ + selector: 'app-solution-form', + templateUrl: './solution-form.component.html', + styleUrls: ['./solution-form.component.scss'], +}) +export class SolutionFormComponent implements OnInit { + issues$: Observable = EMPTY; + + issuesOptions$: Observable = EMPTY; + + constructor( + private solutionFormService: SolutionFormService, + private store: Store + ) {} + + formGroup = new FormGroup({ + issue: new FormControl( + undefined as unknown as IssueModel, + Validators.required + ), + title: new FormControl('', Validators.required), + description: new FormControl('', Validators.required), + }); + + ngOnInit(): void { + this.store.dispatch(receiveAllIssuesAction()); + this.issues$ = this.store.select(selectAllIssues); + this.issuesOptions$ = this.issues$.pipe( + map((issues) => + issues.map((issue) => ({ + label: issue.title, + value: issue, + })) + ), + tap(console.log) + ); + } + submit() { + const form = this.formGroup.getRawValue(); + this.solutionFormService + .createSolution( + form.issue as IssueModel, + form.title || '', + form.description || '' + ) + .subscribe(console.log); + } +} diff --git a/apps/frontend/src/app/modules/solution-form/solution-form.module.ts b/apps/frontend/src/app/modules/solution-form/solution-form.module.ts new file mode 100644 index 0000000..6d9e991 --- /dev/null +++ b/apps/frontend/src/app/modules/solution-form/solution-form.module.ts @@ -0,0 +1,27 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { CardModule } from 'primeng/card'; +import { DropdownModule } from 'primeng/dropdown'; +import { InputTextModule } from 'primeng/inputtext'; +import { InputTextareaModule } from 'primeng/inputtextarea'; +import { ListboxModule } from 'primeng/listbox'; +import { SolutionFormRoutingModule } from './solution-form-routiong.module'; +import { SolutionFormComponent } from './solution-form.component'; +@NgModule({ + declarations: [SolutionFormComponent], + imports: [ + CommonModule, + CardModule, + InputTextModule, + ReactiveFormsModule, + SolutionFormRoutingModule, + ListboxModule, + InputTextareaModule, + ButtonModule, + DropdownModule, + ], + providers: [], +}) +export class SolutionFormModule {} diff --git a/apps/frontend/src/app/services/solutions.service.ts b/apps/frontend/src/app/services/solutions.service.ts index 1af2224..f4cfab0 100644 --- a/apps/frontend/src/app/services/solutions.service.ts +++ b/apps/frontend/src/app/services/solutions.service.ts @@ -1,32 +1,30 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { map } from 'rxjs'; -import { SolutionModel } from '../models/solution.model'; import { CommentModel } from '../models/comment.model'; +import { SolutionModel } from '../models/solution.model'; @Injectable({ providedIn: 'root', }) -export class IssueService { +export class SolutionService { constructor(private http: HttpClient) {} - getSolutions(id: string) { - return this.http.get('/issue/'+id+'/solution').pipe( - map((solution) => - solution.map((solution) => ({ - title: solution.title, - description: solution.description, - comments: solution.comments, - createdAt: solution.createdAt - })) - ) + getSolution(id: string) { + return this.http.get('/issue/' + id + '/solution').pipe( + map((solution) => ({ + title: solution.title, + description: solution.description, + comments: solution.comments, + createdAt: solution.createdAt, + })) ); } } interface SolutionDto { - title: string, - description: string, - comments: CommentModel[], - createdAt: Date + title: string; + description: string; + comments: CommentModel[]; + createdAt: Date; } diff --git a/apps/frontend/src/app/store/actions/issue.actions.ts b/apps/frontend/src/app/store/actions/issue.actions.ts index fd488aa..3f0c001 100644 --- a/apps/frontend/src/app/store/actions/issue.actions.ts +++ b/apps/frontend/src/app/store/actions/issue.actions.ts @@ -11,3 +11,7 @@ export const receivedAllIssuesSuccessAction = createAction( export const receivedAllIssuesErrorAction = createAction( '[issues] received all issues error' ); +export const addIssueAction = createAction( + '[issues] add issue', + props<{ issue: IssueModel }>() +); diff --git a/apps/frontend/src/app/store/actions/solution.actions.ts b/apps/frontend/src/app/store/actions/solution.actions.ts new file mode 100644 index 0000000..689bd06 --- /dev/null +++ b/apps/frontend/src/app/store/actions/solution.actions.ts @@ -0,0 +1,11 @@ +import { createAction, props } from '@ngrx/store'; +import { SolutionModel } from 'src/app/models/solution.model'; + +export const receiveSolutionAction = createAction( + '[solution] receive solution', + props<{ issueId: string }>() +); +export const receivedSolutionSuccessAction = createAction( + '[solution] receive solution', + props<{ issueId: string; solution: SolutionModel }>() +); diff --git a/apps/frontend/src/app/store/effect/issue.effect.ts b/apps/frontend/src/app/store/effect/issue.effect.ts index 5030881..ba83f6d 100644 --- a/apps/frontend/src/app/store/effect/issue.effect.ts +++ b/apps/frontend/src/app/store/effect/issue.effect.ts @@ -10,7 +10,7 @@ import { @Injectable({ providedIn: 'root' }) export class IssueEffect { - preloadAutomaticOffer$ = createEffect(() => { + loadIssues$ = createEffect(() => { return this.actions$.pipe( ofType(receiveAllIssuesAction), switchMap((action) => this.issueService.getIssues()), diff --git a/apps/frontend/src/app/store/effect/solution.effect.ts b/apps/frontend/src/app/store/effect/solution.effect.ts new file mode 100644 index 0000000..3960f14 --- /dev/null +++ b/apps/frontend/src/app/store/effect/solution.effect.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { map, switchMap } from 'rxjs'; +import { SolutionService } from 'src/app/services/solutions.service'; +import { + receiveSolutionAction, + receivedSolutionSuccessAction, +} from '../actions/solution.actions'; + +@Injectable({ providedIn: 'root' }) +export class SolutionEffect { + loadSolution$ = createEffect(() => { + return this.actions$.pipe( + ofType(receiveSolutionAction), + switchMap((action) => + this.solutionService.getSolution(action.issueId).pipe( + map((solution) => + receivedSolutionSuccessAction({ + issueId: action.issueId, + solution, + }) + ) + ) + ) + ); + }); + + constructor( + private actions$: Actions, + private solutionService: SolutionService + ) {} +} diff --git a/apps/frontend/src/app/store/reducer/issue.reducer.ts b/apps/frontend/src/app/store/reducer/issue.reducer.ts index bb60a9b..1e5b59f 100644 --- a/apps/frontend/src/app/store/reducer/issue.reducer.ts +++ b/apps/frontend/src/app/store/reducer/issue.reducer.ts @@ -1,5 +1,9 @@ import { createReducer, on } from '@ngrx/store'; -import { receivedAllIssuesSuccessAction } from '../actions/issue.actions'; +import { + addIssueAction, + receivedAllIssuesSuccessAction, +} from '../actions/issue.actions'; +import { receivedSolutionSuccessAction } from '../actions/solution.actions'; import { IssueState } from '../state/issue.state'; const initialState: IssueState = { @@ -38,8 +42,8 @@ const initialState: IssueState = { solution: { comments: [], createdAt: new Date(), - description: "eoooo", - title: "title" + description: 'eoooo', + title: 'title', }, comments: [], }, @@ -61,5 +65,21 @@ export const issueReducer = createReducer( initialState, on(receivedAllIssuesSuccessAction, (state: IssueState, action) => ({ issues: action.issues, + })), + on(addIssueAction, (state, action) => ({ + issues: [...state.issues, action.issue], + })), + on(receivedSolutionSuccessAction, (state, action) => ({ + issues: { + ...state.issues.slice( + 0, + state.issues.findIndex((issue) => issue.id === action.issueId) + ), + state.issues[0], + + ...state.issues.slice( + state.issues.findIndex((issue) => issue.id === action.issueId) + 1 + ), + }, })) ); diff --git a/apps/frontend/src/environments/environments.prod.ts b/apps/frontend/src/environments/environments.prod.ts new file mode 100644 index 0000000..cb6c68d --- /dev/null +++ b/apps/frontend/src/environments/environments.prod.ts @@ -0,0 +1,3 @@ +export const environments = { + apiUrl: 'http://www.server.pl', +}; diff --git a/apps/frontend/src/environments/environments.ts b/apps/frontend/src/environments/environments.ts new file mode 100644 index 0000000..6f5d035 --- /dev/null +++ b/apps/frontend/src/environments/environments.ts @@ -0,0 +1,4 @@ +// Dev +export const environments = { + apiUrl: 'http://localhost', +};