diff --git a/api/routes/users.js b/api/routes/users.js index 95f7694..6e98986 100644 --- a/api/routes/users.js +++ b/api/routes/users.js @@ -171,6 +171,61 @@ users.route('/:id') }) }) + .put(apiAuthenticationMiddleware, (req, res,next) => { + // first check that the given element id is the same as the URL id + if (!req.body || req.body._id !== req.params.id) { + // the URL does not fit the given element + var err = new Error('id of PATCH resource and send JSON body are not equal ' + req.params.id + " " + req.body._id); + err.status = codes.notfound; + next(err); + return; // prevent node to process this function further after next() has finished. + } + // main difference of PUT and PATCH is that PUT expects all data in request: checked by using the schema + var video = new UserModel(req.body); + UserModel.findById(req.params.id, req.body, {new: true}, function (err, item) { + // with parameter {new: true} the TweetNModel will return the new and changed object from the DB and not the old one. + if (err) { + err.status = codes.wrongrequest; + return next(err); + } + else if (!item) { + err = new Error("item not found"); + err.status = codes.notfound; + return next(err); + } + // optional task 3b: check that version is still accurate + else if (video.__v !== item.__v) { + err = new Error("version outdated. Meanwhile update on item happened. Please GET resource again") + err.status = codes.conflict; + return next(err); + } + // now update all fields in DB item with body data in variable video + for (var field in UserModel.schema.paths) { + if ((field !== '_id') && (field !== '__v')) { + // this includes undefined. is important to reset attributes that are missing in req.body + item.set(field, video[field]); + } + } + + // optional task 3: update updatedAt and increase version + item.updatedAt = new Date(); + item.increment(); // this sets __v++ + item.save(function (err) { + if (!err) { + res.locals.items = item; + } else { + err.status = codes.wrongrequest; + err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors); + } + getExtendedUser(item, next, (extUser) => { + res.locals.items = extUser; + res.locals.processed = true; + return next(); + }) + }); + }) + }) + .delete(apiAuthenticationMiddleware, (req, res, next) => { UserModel.findByIdAndRemove(req.params.id, (err, item) => { if (err) { @@ -241,7 +296,7 @@ let getExtendedUser = (user, next, callback) => { }) }) } else { - extUser.rank = null; + extUser.rank = {level: user.rankLvl}; addAwards(extUser).then(() => { callback(extUser); }) diff --git a/static/src/app/app.component.css b/static/src/app/app.component.css index fcdca7f..156bb18 100644 --- a/static/src/app/app.component.css +++ b/static/src/app/app.component.css @@ -20,5 +20,5 @@ li { .left { float: left; - width: calc(100% - 300px); + width: calc(100% - 400px); } diff --git a/static/src/app/services/http-client.ts b/static/src/app/services/http-client.ts index 0fa536e..088ad03 100644 --- a/static/src/app/services/http-client.ts +++ b/static/src/app/services/http-client.ts @@ -39,6 +39,13 @@ export class HttpClient { }); } + put(url, data) { + let headers = this.createAuthorizationHeader(); + return this.http.put(url, data, { + headers: headers + }); + } + patch(url, data) { let headers = this.createAuthorizationHeader(); return this.http.patch(url, data, { diff --git a/static/src/app/services/user-service/user.service.ts b/static/src/app/services/user-service/user.service.ts index 0692199..cb212f0 100644 --- a/static/src/app/services/user-service/user.service.ts +++ b/static/src/app/services/user-service/user.service.ts @@ -48,7 +48,7 @@ export class UserService { } updateUser(user) { - return this.http.patch(this.config.apiUrl + this.config.apiUserPath + user._id, user) + return this.http.put(this.config.apiUrl + this.config.apiUserPath + user._id, user) .map(res => res.json()) .do(savedUser => { const action = {type: EDIT, data: savedUser}; diff --git a/static/src/app/users/user-award/user-award.component.css b/static/src/app/users/user-award/user-award.component.css new file mode 100644 index 0000000..789b2f5 --- /dev/null +++ b/static/src/app/users/user-award/user-award.component.css @@ -0,0 +1,4 @@ +.decoration-preview { + background-color: white; + padding: 5px; +} diff --git a/static/src/app/users/user-award/user-award.component.html b/static/src/app/users/user-award/user-award.component.html new file mode 100644 index 0000000..006fe4c --- /dev/null +++ b/static/src/app/users/user-award/user-award.component.html @@ -0,0 +1,127 @@ +
+

Teilnehmer auszeichnen

+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ + + + + +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
GrafikBezeichnungBegründungDatumLÖschen
+ 1 + + 2016-04-16 12:02:12 + + Herman + Melville + + 222 + + Paid online + + + + + + + +
+ + +
+
+
+
+
+ + +
diff --git a/static/src/app/users/user-award/user-award.component.ts b/static/src/app/users/user-award/user-award.component.ts new file mode 100644 index 0000000..dd0fb61 --- /dev/null +++ b/static/src/app/users/user-award/user-award.component.ts @@ -0,0 +1,134 @@ +import {Component, ViewChild} from "@angular/core"; +import {ActivatedRoute, Router} from "@angular/router"; +import * as model from "../../models/model-interfaces"; +import {Rank, Squad, User} from "../../models/model-interfaces"; +import {UserService} from "../../services/user-service/user.service"; +import {SquadService} from "../../services/squad-service/squad.service"; +import {RankService} from "../../services/rank-service/rank.service"; +import {Subscription} from "rxjs"; +import {NgForm} from "@angular/forms"; + + +@Component({ + templateUrl: './user-award.component.html', + styleUrls: ['./user-award.component.css', '../../style/new-entry-form.css'], +}) +export class UserAwardComponent { + + @ViewChild(NgForm) form: NgForm; + + subscription: Subscription; + + id: string; + + model = model; + + showSuccessLabel = false; + + ranksDisplay = 'none'; + + user: User = {squad: {}}; + + squads: Squad[] = []; + + ranks: Rank[] = []; + + saved = false; + + constructor(private router: Router, + private route: ActivatedRoute, + private userService: UserService, + private squadService: SquadService, + private rankService: RankService) { + } + + ngOnInit() { + + // this.subscription = this.route.params + // .map(params => params['id']) + // .filter(id => id != undefined) + // .flatMap(id => this.userService.getUser(id)) + // .subscribe(user => { + // if (user.squad === null) { + // user.squad = "0"; + // this.ranksDisplay = 'none'; + // } else { + // this.rankService.findRanks('', user.squad.fraction).subscribe(ranks => { + // this.ranks = ranks; + // this.ranksDisplay = 'block'; + // }); + // } + // this.user = user; + // }); + // + // this.squadService.findSquads().subscribe(squads => { + // this.squads = squads; + // }); + } + + ngOnDestroy() { + // this.subscription.unsubscribe(); + } + + toggleRanks() { + if (this.user.squad != '0') { + this.rankService.findRanks('', this.user.squad.fraction).subscribe( + ranks => { + this.ranks = ranks; + this.ranksDisplay = 'block'; + } + ); + } else { + this.ranksDisplay = 'none'; + } + } + + saveUser(rankLevel) { + const updateObject = { + _id: this.user._id, + username: this.user.username, + squadId: this.user.squad._id, + rankLvl: rankLevel + }; + this.userService.updateUser(updateObject) + .subscribe(user => { + this.user = user; + this.showSuccessLabel = true; + setTimeout(() => { + this.showSuccessLabel = false; + }, 2000) + }) + } + + + cancel() { + this.router.navigate(['../..'], {relativeTo: this.route}); + return false; + } + + /** + * compare ngValue with ngModel to assign selected element + */ + equals(o1: Squad, o2: Squad) { + if (o1 && o2) { + return o1._id === o2._id; + } + } + + deleteUser(confirm) { + if (confirm.toLowerCase() === this.user.username.toLocaleLowerCase()) { + this.userService.deleteUser(this.user) + .subscribe((res) => { + this.router.navigate(['../..'], {relativeTo: this.route}); + }) + } + } + + canDeactivate(): boolean { + if (this.saved || !this.form.dirty) { + return true; + } + return window.confirm(`Ihr Formular besitzt ungespeicherte Änderungen, möchten Sie die Seite wirklich verlassen?`); + } + +} diff --git a/static/src/app/users/user-list/user-item.component.css b/static/src/app/users/user-list/user-item.component.css index ca97142..57348aa 100644 --- a/static/src/app/users/user-list/user-item.component.css +++ b/static/src/app/users/user-list/user-item.component.css @@ -3,7 +3,6 @@ div.user-list-entry, a.user-list-entry { width: 475px; border-radius: 2px; border: lightgrey solid 1px; - cursor: pointer; margin-bottom: -1px; } @@ -11,23 +10,36 @@ div.user-list-entry, a.user-list-entry { background: lightgrey; } +.icon-award { + background: url(../../../assets/award.png); + width: 18px; + height: 29px; + display: block; + margin-left: 12px; +} + span { cursor: pointer; } a { - font-size: x-large; - font-weight: 700; + font-size: large; + font-weight: 600; } small { color: grey; + font-size: x-small; } .trash { - padding-top: 18px; - font-size: 17px; - margin-left: -10px; + font-size: 19px; + margin-left: 12px; + margin-top: 2px; +} + +.edit { + font-size: 22px; } .selected { diff --git a/static/src/app/users/user-list/user-item.component.html b/static/src/app/users/user-list/user-item.component.html index 41c414b..2ad44e0 100644 --- a/static/src/app/users/user-list/user-item.component.html +++ b/static/src/app/users/user-list/user-item.component.html @@ -1,14 +1,20 @@ -
+
-
+
- {{user.username}} + {{user.username}}
CSAT - {{user.squad.name}} NATO - {{user.squad.name}} ohne Squad/Fraktion
+ +
+ + + +
diff --git a/static/src/app/users/user-list/user-item.component.ts b/static/src/app/users/user-list/user-item.component.ts index 22e660d..4289756 100644 --- a/static/src/app/users/user-list/user-item.component.ts +++ b/static/src/app/users/user-list/user-item.component.ts @@ -8,7 +8,7 @@ import {User} from "../../models/model-interfaces"; styleUrls: ['./user-item.component.css'], changeDetection: ChangeDetectionStrategy.OnPush, inputs: ['user', 'selected'], - outputs: ['userSelected', 'userDelete'] + outputs: ['userSelected', 'userAward', 'userDelete'] }) export class UserItemComponent { @@ -16,6 +16,7 @@ export class UserItemComponent { user: User; userSelected = new EventEmitter(); + userAward = new EventEmitter(); userDelete = new EventEmitter(); constructor(private router: Router) { @@ -26,6 +27,10 @@ export class UserItemComponent { this.userSelected.emit(this.user._id) } + award() { + this.userAward.emit(this.user._id) + } + delete() { this.userDelete.emit(this.user); } diff --git a/static/src/app/users/user-list/user-list.component.html b/static/src/app/users/user-list/user-list.component.html index 69ecd35..23e2902 100644 --- a/static/src/app/users/user-list/user-list.component.html +++ b/static/src/app/users/user-list/user-list.component.html @@ -44,6 +44,7 @@ [user]="user" (userDelete)="deleteUser(user)" (userSelected)="selectUser($event)" + (userAward)="awardUser($event)" [selected]="user._id == selectedUserId">
diff --git a/static/src/app/users/user-list/user-list.component.ts b/static/src/app/users/user-list/user-list.component.ts index ef230f2..b9d948b 100644 --- a/static/src/app/users/user-list/user-list.component.ts +++ b/static/src/app/users/user-list/user-list.component.ts @@ -51,11 +51,16 @@ export class UserListComponent implements OnInit { this.router.navigate([{outlets: {'right': ['new']}}], {relativeTo: this.route}); } - selectUser(userId: string | number) { + selectUser(userId: string) { this.selectedUserId = userId; this.router.navigate([{outlets: {'right': ['overview', userId]}}], {relativeTo: this.route}); } + awardUser(userId: string) { + this.selectedUserId = userId; + this.router.navigate([{outlets: {'right': ['award', userId]}}], {relativeTo: this.route}); + } + filterUsersByFraction(query = '', fractionFilter) { this.users$ = this.userService.findUsers(query, fractionFilter); } diff --git a/static/src/app/users/user-overview/user-overview.component.html b/static/src/app/users/user-overview/user-overview.component.html index 9736f40..87f17e6 100644 --- a/static/src/app/users/user-overview/user-overview.component.html +++ b/static/src/app/users/user-overview/user-overview.component.html @@ -1,200 +1,57 @@ -
-

Teilnehmer-Details - - Erfolgreich gespeichert - -

-
-
+
+

Teilnehmer editieren

+
+ + -
-
-
- -
-
- {{user.username}} -
-
- -
- -
-
-
- -
-
- Ohne Fraktion -
-
- CSAT -
-
- NATO -
-
-
-
-
- -
-
-
-
- -
-
Ohne Squad
-
{{user.squad.name}}
-
- -
- -
-
- -
-
-
-
- -
-
Ohne Rang
-
{{user.rank.name}}
-
- -
-
- -
-
- -
-
- -
-
- -
-
-   -
-
- -
-
-   -
-
- -
-
-   -
-
-   -
-
- -
-
-   -
-
- -
- -
-
- -
- -
-
-
- Bild -
-
- Bezeichnung -
-
- Begründung -
-
- Datum -
-
-   -
-
-
-
- -
-
- -
- -
- {{award.decorationId.name}} -
-
- {{award.reason}} -
-
- {{award.date | date: 'dd.MM.yyyy'}} -
-
- -
-
-
- -
-
-
-
- -
-
- -
-
-   -
- - -
-
- -
+
-
+ +
+ + + + +
+ +
+ + + + +
+ + + + + diff --git a/static/src/app/users/user-overview/user-overview.component.ts b/static/src/app/users/user-overview/user-overview.component.ts index 2b852a2..2d6e921 100644 --- a/static/src/app/users/user-overview/user-overview.component.ts +++ b/static/src/app/users/user-overview/user-overview.component.ts @@ -1,87 +1,125 @@ -import {Component} from "@angular/core"; +import {Component, ViewChild} from "@angular/core"; import {ActivatedRoute, Router} from "@angular/router"; import * as model from "../../models/model-interfaces"; -import {Decoration, Squad, User} from "../../models/model-interfaces"; +import {Rank, Squad, User} from "../../models/model-interfaces"; import {UserService} from "../../services/user-service/user.service"; import {SquadService} from "../../services/squad-service/squad.service"; -import {DecorationService} from "../../services/decoration-service/decoration.service"; -import {AwardingService} from "../../services/awarding-service/awarding.service"; +import {RankService} from "../../services/rank-service/rank.service"; +import {Subscription} from "rxjs"; +import {NgForm} from "@angular/forms"; @Component({ templateUrl: './user-overview.component.html', - styleUrls: ['./user-overview.component.css', '../../style/overview.css'], + styleUrls: ['./user-overview.component.css', '../../style/new-entry-form.css'], }) export class UserOverviewComponent { + @ViewChild(NgForm) form: NgForm; + + subscription: Subscription; + id: string; model = model; showSuccessLabel = false; - decoPreviewDisplay = 'none'; + ranksDisplay = 'none'; - user: User; + user: User = {squad: {}}; - squads: Squad[]; + squads: Squad[] = []; - decorations: Decoration[]; + ranks: Rank[] = []; + + saved = false; constructor(private router: Router, private route: ActivatedRoute, private userService: UserService, private squadService: SquadService, - private decorationService: DecorationService, - private awardingService: AwardingService) { + private rankService: RankService) { } ngOnInit() { - this.route.params.subscribe((params) => { - this.userService.getUser(params['id']).subscribe(user => { + + this.subscription = this.route.params + .map(params => params['id']) + .filter(id => id != undefined) + .flatMap(id => this.userService.getUser(id)) + .subscribe(user => { + console.log(user.squad) + if (!user.squad) { + user.squad = "0"; + this.ranksDisplay = 'none'; + } else { + this.rankService.findRanks('', user.squad.fraction).subscribe(ranks => { + this.ranks = ranks; + this.ranksDisplay = 'block'; + }); + } this.user = user; }); - }); this.squadService.findSquads().subscribe(squads => { this.squads = squads; }); - - this.decorationService.findDecorations().subscribe(decorations => { - this.decorations = decorations; - }); } - toggleDecoPreview(descriptionField, decorationId, image) { - this.decoPreviewDisplay = 'flex'; // visible & keep same height for all children - - const description = this.decorations.find( - decoration => decoration._id === decorationId - ).description; - - image.src = 'resource/decoration/' + decorationId + '.png'; - descriptionField.innerHTML = description; - + ngOnDestroy() { + this.subscription.unsubscribe(); } - update(attrName, value, inputField?) { - if (attrName === 'squadId' && value === '---') { - value = null; + toggleRanks() { + if (this.user.squad != '0') { + this.rankService.findRanks('', this.user.squad.fraction).subscribe( + ranks => { + this.ranks = ranks; + this.ranksDisplay = 'block'; + } + ); + } else { + this.ranksDisplay = 'none'; } - if (value !== '' && (attrName !== 'rankLvl' || attrName === 'rankLvl' && value >= 0 && value <= 22)) { - const updateObject = {_id: this.user._id}; - updateObject[attrName] = value; - this.userService.updateUser(updateObject) - .subscribe(user => { - this.user = user; - if (inputField) { - inputField.value = ''; - } - this.showSuccessLabel = true; - setTimeout(() => { - this.showSuccessLabel = false; - }, 2000) - }) + } + + saveUser(rankLevel) { + const updateObject = { + _id: this.user._id, + username: this.user.username, + rankLvl: rankLevel, + squadId: null + }; + if (this.user.squad._id !== '0') { + updateObject.squadId = this.user.squad._id + } + console.log(updateObject) + this.userService.updateUser(updateObject) + .subscribe(user => { + if (!user.squad) { + user.squad = '0'; + } + this.user = user; + this.showSuccessLabel = true; + setTimeout(() => { + this.showSuccessLabel = false; + }, 2000) + }) + } + + + cancel() { + this.router.navigate(['../..'], {relativeTo: this.route}); + return false; + } + + /** + * compare ngValue with ngModel to assign selected element + */ + equals(o1: Squad, o2: Squad) { + if (o1 && o2) { + return o1._id === o2._id; } } @@ -94,47 +132,11 @@ export class UserOverviewComponent { } } - deleteAwarding(awardingId) { - this.awardingService.deleteAwarding(awardingId).subscribe((res) => { - this.awardingService.getUserAwardings(this.user._id) - .map((res) => res.json()) - .subscribe((awards) => { - this.user.awards = awards; - this.showSuccessLabel = true; - setTimeout(() => { - this.showSuccessLabel = false; - }, 2000) - }) - }) - } - - addAwarding(decorationField, reasonField, previewImage, descriptionField) { - const decorationId = decorationField.value; - const reason = reasonField.value; - if (decorationId && reason.length > 0) { - const award = { - "userId": this.user._id, - "decorationId": decorationId, - "reason": reason, - "date": Date.now() - }; - this.awardingService.addAwarding(award).subscribe(() => { - this.awardingService.getUserAwardings(this.user._id) - .map((res) => res.json()) - .subscribe(awards => { - this.user.awards = awards; - this.decoPreviewDisplay = 'none'; - decorationField.value = undefined; - reasonField.value = ''; - previewImage.src = ''; - descriptionField.innerHTML = ''; - this.showSuccessLabel = true; - setTimeout(() => { - this.showSuccessLabel = false; - }, 2000) - }) - }) + canDeactivate(): boolean { + if (this.saved || !this.form.dirty) { + return true; } + return window.confirm(`Ihr Formular besitzt ungespeicherte Änderungen, möchten Sie die Seite wirklich verlassen?`); } } diff --git a/static/src/app/users/users.routing.ts b/static/src/app/users/users.routing.ts index 421b2ac..e976a8f 100644 --- a/static/src/app/users/users.routing.ts +++ b/static/src/app/users/users.routing.ts @@ -3,6 +3,7 @@ import {UsersComponent} from "./users.component"; import {UserOverviewComponent} from "./user-overview/user-overview.component"; import {UserListComponent} from "./user-list/user-list.component"; import {CreateUserComponent} from "./new-user/new-user.component"; +import {UserAwardComponent} from "./user-award/user-award.component"; export const usersRoutes: Routes = [{ path: '', component: UsersComponent, @@ -22,6 +23,12 @@ export const usersRoutes: Routes = [{ path: 'overview/:id', component: UserOverviewComponent, outlet: 'right' - }]; + }, + { + path: 'award/:id', + component: UserAwardComponent, + outlet: 'right' + } +]; -export const usersRoutingComponents = [UsersComponent, UserListComponent, UserOverviewComponent, CreateUserComponent]; +export const usersRoutingComponents = [UsersComponent, UserListComponent, UserOverviewComponent, CreateUserComponent, UserAwardComponent]; diff --git a/static/src/assets/award.png b/static/src/assets/award.png new file mode 100644 index 0000000..5bc342a Binary files /dev/null and b/static/src/assets/award.png differ