Merge pull request 'SES-160 shop on gm and player screen' (#85) from SES-160 into dev

Reviewed-on: #85
This commit is contained in:
Natalia Gawron 2021-01-23 21:50:01 +01:00
commit 55a77aec8c
37 changed files with 1381 additions and 54 deletions

View File

@ -9,7 +9,11 @@ using System.Threading.Tasks;
namespace SessionCompanion.Services.Interfaces namespace SessionCompanion.Services.Interfaces
{ {
using SessionCompanion.Extensions.EitherType;
using SessionCompanion.ViewModels.ApiResponses;
public interface IShopkeeperItemService : IServiceBase<ShopkeeperItemViewModel, ShopkeeperItem> public interface IShopkeeperItemService : IServiceBase<ShopkeeperItemViewModel, ShopkeeperItem>
{ {
Task<Either<SuccessResponse, ErrorResponse>> GetActiveShkopkeeperWithItems(int shopkeeperId, int amount, int? weaponId, int? armorId);
} }
} }

View File

@ -15,5 +15,6 @@ namespace SessionCompanion.Services.Interfaces
{ {
Task<Either<SuccessResponse, ErrorResponse>> CreateNewShopKeeper(ShopkeeperWithItemsViewModel shopkeeperWithItemsViewModel); Task<Either<SuccessResponse, ErrorResponse>> CreateNewShopKeeper(ShopkeeperWithItemsViewModel shopkeeperWithItemsViewModel);
Task<Either<SuccessResponse, ErrorResponse>> ChangeShopkeeperStatus(int shopkeeperId, bool availability); Task<Either<SuccessResponse, ErrorResponse>> ChangeShopkeeperStatus(int shopkeeperId, bool availability);
Task<Either<ShopkeeperWithItemsDetailsViewModel, ErrorResponse>> GetActiveShkopkeeperWithItems();
} }
} }

View File

@ -9,11 +9,17 @@ using System.Threading.Tasks;
namespace SessionCompanion.Services.Profiles namespace SessionCompanion.Services.Profiles
{ {
using SessionCompanion.ViewModels.ShopkeeperItemViewModels;
public class ShopkeeperItemsProfile : Profile public class ShopkeeperItemsProfile : Profile
{ {
public ShopkeeperItemsProfile() public ShopkeeperItemsProfile()
{ {
CreateMap<ShopkeeperItemViewModel, ShopkeeperItem>().ReverseMap(); CreateMap<ShopkeeperItemViewModel, ShopkeeperItem>().ReverseMap();
CreateMap<ShopkeeperItemDetailsViewModel, ShopkeeperItem>()
.ForMember(vm => vm.Armor, conf => conf.MapFrom(item => item.Armor))
.ForMember(vm => vm.Weapon, conf => conf.MapFrom(item => item.Weapon)).ReverseMap();
} }
} }
} }

View File

@ -16,6 +16,9 @@ namespace SessionCompanion.Services.Profiles
CreateMap<ShopkeeperViewModel, Shopkeeper>().ReverseMap(); CreateMap<ShopkeeperViewModel, Shopkeeper>().ReverseMap();
CreateMap<Shopkeeper, ShopkeeperWithItemsViewModel>() CreateMap<Shopkeeper, ShopkeeperWithItemsViewModel>()
.ForMember(vm => vm.Items, conf => conf.MapFrom(items => items.ShopkeeperItems)).ReverseMap(); .ForMember(vm => vm.Items, conf => conf.MapFrom(items => items.ShopkeeperItems)).ReverseMap();
CreateMap<Shopkeeper, ShopkeeperWithItemsDetailsViewModel>()
.ForMember(vm => vm.Items, conf => conf.MapFrom(items => items.ShopkeeperItems)).ReverseMap();
} }
} }
} }

View File

@ -5,16 +5,56 @@ using SessionCompanion.Services.Base;
using SessionCompanion.Services.Interfaces; using SessionCompanion.Services.Interfaces;
using SessionCompanion.ViewModels.ShopkeeperItemsViewModels; using SessionCompanion.ViewModels.ShopkeeperItemsViewModels;
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace SessionCompanion.Services.Services namespace SessionCompanion.Services.Services
{ {
using System.Linq;
using Microsoft.EntityFrameworkCore;
using SessionCompanion.Extensions.EitherType;
using SessionCompanion.ViewModels.ApiResponses;
using SessionCompanion.ViewModels.ShopkeeperViewModels;
public class ShopkeeperItemService : ServiceBase<ShopkeeperItemViewModel, ShopkeeperItem>, IShopkeeperItemService public class ShopkeeperItemService : ServiceBase<ShopkeeperItemViewModel, ShopkeeperItem>, IShopkeeperItemService
{ {
public ShopkeeperItemService(IMapper mapper, IRepository<ShopkeeperItem> repository) : base(mapper, repository) public ShopkeeperItemService(IMapper mapper, IRepository<ShopkeeperItem> repository) : base(mapper, repository)
{ } { }
public async Task<Either<SuccessResponse, ErrorResponse>> GetActiveShkopkeeperWithItems(int shopkeeperId, int amount, int? weaponId, int? armorId)
{
try
{
var shopkeeperItem = await Repository.Get(c => c.ShopkeeperId.Equals(shopkeeperId) && c.WeaponId.Equals(weaponId) && c.ArmorId.Equals(armorId)).FirstOrDefaultAsync();
if (shopkeeperItem == null)
{
throw new Exception("Shopkeeper with given id's couldn't have been found");
}
if (shopkeeperItem.Amount - amount < 0)
{
throw new Exception("Shopkeeper does not have that quantity of the item");
}
else if (shopkeeperItem.Amount - amount == 0)
{
this.Repository.Delete(shopkeeperItem);
await this.Repository.Save();
return new SuccessResponse("Items were deducted from the shopkeeper");
}
else
{
shopkeeperItem.Amount -= amount;
await this.Repository.Update(shopkeeperItem);
await this.Repository.Save();
return new SuccessResponse("Items were deducted from the shopkeeper");
}
}
catch (Exception e)
{
return new ErrorResponse() { StatusCode = 500, Message = e.Message };
}
}
} }
} }

View File

@ -19,6 +19,7 @@ namespace SessionCompanion.Services.Services
{ {
public ShopkeeperService(IMapper mapper, IRepository<Shopkeeper> repository) : base(mapper, repository) public ShopkeeperService(IMapper mapper, IRepository<Shopkeeper> repository) : base(mapper, repository)
{ } { }
/// <summary> /// <summary>
/// Funkcja zmienia status sprzedawcy /// Funkcja zmienia status sprzedawcy
/// </summary> /// </summary>
@ -41,26 +42,27 @@ namespace SessionCompanion.Services.Services
await Repository.Update(shopkeeper); await Repository.Update(shopkeeper);
} }
await Repository.Save(); await Repository.Save();
return new SuccessResponse("Shopkeepers updated") { StatusCode = 200 }; return new SuccessResponse(200, "Shopkeepers updated");
} }
var newActiveShopkeeper = shopkeepers.Where(c => c.Id.Equals(shopkeeperId)).Single(); var newActiveShopkeeper = shopkeepers.Where(c => c.Id.Equals(shopkeeperId)).Single();
newActiveShopkeeper.IsAvailable = false; newActiveShopkeeper.IsAvailable = false;
await Repository.Update(newActiveShopkeeper); await Repository.Update(newActiveShopkeeper);
await Repository.Save(); await Repository.Save();
return new SuccessResponse("Shopkeepers updated") { StatusCode = 200 }; return new SuccessResponse(200, "Shopkeepers updated");
} }
catch (Exception e) catch (Exception e)
{ {
return new ErrorResponse() { StatusCode = 500, Message = e.Message }; return new ErrorResponse() { StatusCode = 500, Message = e.Message };
} }
} }
public async Task<Either<SuccessResponse, ErrorResponse>> CreateNewShopKeeper(ShopkeeperWithItemsViewModel shopkeeperWithItemsViewModel) public async Task<Either<SuccessResponse, ErrorResponse>> CreateNewShopKeeper(ShopkeeperWithItemsViewModel shopkeeperWithItemsViewModel)
{ {
try try
{ {
var activeShopkeeper = await Repository.Get(c => c.IsAvailable.Equals(true)).SingleAsync(); var activeShopkeeper = await Repository.Get(c => c.IsAvailable.Equals(true)).FirstOrDefaultAsync();
if (activeShopkeeper is not null && shopkeeperWithItemsViewModel.IsAvailable) if (activeShopkeeper != null && shopkeeperWithItemsViewModel.IsAvailable)
{ {
activeShopkeeper.IsAvailable = false; activeShopkeeper.IsAvailable = false;
await Repository.Update(activeShopkeeper); await Repository.Update(activeShopkeeper);
@ -75,5 +77,23 @@ namespace SessionCompanion.Services.Services
return new ErrorResponse() { StatusCode = 500, Message = e.Message }; return new ErrorResponse() { StatusCode = 500, Message = e.Message };
} }
} }
public async Task<Either<ShopkeeperWithItemsDetailsViewModel, ErrorResponse>> GetActiveShkopkeeperWithItems()
{
try
{
var activeShopkeeper = await Repository.Get(c => c.IsAvailable.Equals(true))
.Include(t => t.ShopkeeperItems)
.ThenInclude(t => t.Armor)
.Include(t => t.ShopkeeperItems)
.ThenInclude(t => t.Weapon).FirstOrDefaultAsync();
var result = Mapper.Map<ShopkeeperWithItemsDetailsViewModel>(activeShopkeeper);
return result;
}
catch (Exception e)
{
return new ErrorResponse() { StatusCode = 500, Message = e.Message };
}
}
} }
} }

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SessionCompanion.ViewModels.ShopkeeperItemViewModels
{
using SessionCompanion.ViewModels.ArmorViewModels;
using SessionCompanion.ViewModels.WeaponViewModels;
public class ShopkeeperItemDetailsViewModel
{
/// <summary>
/// Id Sprzedawcy
/// </summary>
public int? ShopkeeperId { get; set; }
/// <summary>
/// Id zbroi
/// </summary>
public int? ArmorId { get; set; }
public ArmorViewModel Armor { get; set; }
/// <summary>
/// Id broni
/// </summary>
public int? WeaponId { get; set; }
public WeaponViewModel Weapon { get; set; }
/// <summary>
/// Ilość przedmiotu
/// </summary>
public int Amount { get; set; }
}
}

View File

@ -6,20 +6,26 @@ using System.Threading.Tasks;
namespace SessionCompanion.ViewModels.ShopkeeperItemsViewModels namespace SessionCompanion.ViewModels.ShopkeeperItemsViewModels
{ {
using SessionCompanion.ViewModels.ArmorViewModels;
using SessionCompanion.ViewModels.WeaponViewModels;
public class ShopkeeperItemViewModel public class ShopkeeperItemViewModel
{ {
/// <summary> /// <summary>
/// Id Sprzedawcy /// Id Sprzedawcy
/// </summary> /// </summary>
public int? ShopkeeperId { get; set; } public int? ShopkeeperId { get; set; }
/// <summary> /// <summary>
/// Id zbroi /// Id zbroi
/// </summary> /// </summary>
public int? ArmorId { get; set; } public int? ArmorId { get; set; }
/// <summary> /// <summary>
/// Id broni /// Id broni
/// </summary> /// </summary>
public int? WeaponId { get; set; } public int? WeaponId { get; set; }
/// <summary> /// <summary>
/// Ilość przedmiotu /// Ilość przedmiotu
/// </summary> /// </summary>

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SessionCompanion.ViewModels.ShopkeeperViewModels
{
using SessionCompanion.ViewModels.ShopkeeperItemViewModels;
public class ShopkeeperWithItemsDetailsViewModel
{
public int? Id { get; set; }
/// <summary>
/// Nazwa sklepikarza
/// </summary>
public string Name { get; set; }
/// <summary>
/// Status sklepikarza
/// </summary>
public bool IsAvailable { get; set; }
/// <summary>
/// Lista przedmiotów danego sklepikarza
/// </summary>
public IEnumerable<ShopkeeperItemDetailsViewModel> Items { get; set; }
}
}

View File

@ -28,6 +28,7 @@ import {
MatDialogModule, MatDialogModule,
MatTooltipModule, MatTooltipModule,
MatSnackBarModule, MatSnackBarModule,
MatStepperModule,
} from '@angular/material'; } from '@angular/material';
import { UserService } from '../services/user.service'; import { UserService } from '../services/user.service';
import { StoreModule } from '@ngrx/store'; import { StoreModule } from '@ngrx/store';
@ -52,7 +53,7 @@ import { MonsterService } from '../services/monster.service';
import { SpellDetailsDialogComponent } from './components/spell-details-dialog/spell-details-dialog.component'; import { SpellDetailsDialogComponent } from './components/spell-details-dialog/spell-details-dialog.component';
import { GameMasterShopkeepersTableComponent } from './components/game-master-shopkeepers-table/game-master-shopkeepers-table.component'; import { GameMasterShopkeepersTableComponent } from './components/game-master-shopkeepers-table/game-master-shopkeepers-table.component';
import { PlayerWeaponsTableComponent } from './components/player-weapons-table/player-weapons-table.component'; import { PlayerWeaponsTableComponent } from './components/player-weapons-table/player-weapons-table.component';
import {EquipmentService} from "../services/equipment.service"; import { EquipmentService } from '../services/equipment.service';
import { PlayerArmorsTableComponent } from './components/player-armors-table/player-armors-table.component'; import { PlayerArmorsTableComponent } from './components/player-armors-table/player-armors-table.component';
import { PlayerOtherEquipmentTableComponent } from './components/player-other-equipment-table/player-other-equipment-table.component'; import { PlayerOtherEquipmentTableComponent } from './components/player-other-equipment-table/player-other-equipment-table.component';
import { SendMessageActionComponent } from './components/game-master-character-actions-dialog/actions-components/send-message-action/send-message-action.component'; import { SendMessageActionComponent } from './components/game-master-character-actions-dialog/actions-components/send-message-action/send-message-action.component';
@ -64,6 +65,9 @@ import { DragDropModule } from '@angular/cdk/drag-drop';
import { ChooseMonsterDialogComponent } from './components/choose-monster-dialog/choose-monster-dialog.component'; import { ChooseMonsterDialogComponent } from './components/choose-monster-dialog/choose-monster-dialog.component';
import { CreateCharacterComponent } from './components/create-character/create-character.component'; import { CreateCharacterComponent } from './components/create-character/create-character.component';
import { PersonalizeTemplateComponent } from './components/personalize-template/personalize-template.component'; import { PersonalizeTemplateComponent } from './components/personalize-template/personalize-template.component';
import { ShopkeeperService } from '../services/shopkeeper.service';
import { NewShopkeeperDialogComponent } from './components/game-master-shopkeepers-table/shopkeeper-dialogs/new-shopkeeper-dialog/new-shopkeeper-dialog.component';
import { PlayerShopComponent } from './components/player-shop/player-shop.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -92,6 +96,8 @@ import { PersonalizeTemplateComponent } from './components/personalize-template/
MessageDialogComponent, MessageDialogComponent,
GameMasterTurntrackerComponent, GameMasterTurntrackerComponent,
ChooseMonsterDialogComponent, ChooseMonsterDialogComponent,
NewShopkeeperDialogComponent,
PlayerShopComponent,
CreateCharacterComponent, CreateCharacterComponent,
PersonalizeTemplateComponent, PersonalizeTemplateComponent,
], ],
@ -125,6 +131,7 @@ import { PersonalizeTemplateComponent } from './components/personalize-template/
DynamicModule, DynamicModule,
MatSnackBarModule, MatSnackBarModule,
DragDropModule, DragDropModule,
MatStepperModule,
], ],
providers: [ providers: [
UserService, UserService,
@ -135,6 +142,7 @@ import { PersonalizeTemplateComponent } from './components/personalize-template/
OtherEquipmentService, OtherEquipmentService,
MonsterService, MonsterService,
EquipmentService, EquipmentService,
ShopkeeperService,
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
entryComponents: [ entryComponents: [
@ -155,6 +163,8 @@ import { PersonalizeTemplateComponent } from './components/personalize-template/
MessageDialogComponent, MessageDialogComponent,
GameMasterTurntrackerComponent, GameMasterTurntrackerComponent,
ChooseMonsterDialogComponent, ChooseMonsterDialogComponent,
NewShopkeeperDialogComponent,
PlayerShopComponent,
CreateCharacterComponent, CreateCharacterComponent,
PersonalizeTemplateComponent, PersonalizeTemplateComponent,
], ],

View File

@ -17,7 +17,6 @@ export class SendMessageActionComponent implements OnInit {
ngOnInit() {} ngOnInit() {}
SendMessage(message: string) { SendMessage(message: string) {
debugger;
this.signalRService.SendMessageToPlayer(this.characterId, message); this.signalRService.SendMessageToPlayer(this.characterId, message);
this.Close(); this.Close();
} }

View File

@ -31,7 +31,6 @@ export class GameMasterCharacterActionsDialogComponent implements OnInit {
this.characterId = this.data.characterid; this.characterId = this.data.characterid;
this.characterName = this.data.characterName; this.characterName = this.data.characterName;
this.inputs = { characterId: this.characterId }; this.inputs = { characterId: this.characterId };
console.log(this.inputs);
} }
ChangeActionComponent(componentName: string): void { ChangeActionComponent(componentName: string): void {

View File

@ -105,6 +105,7 @@ export class GameMasterDashboardComponent implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
this.signalRService.Login(); this.signalRService.Login();
this.UpdateCharactersList();
} }
UpdateSidenavStatus(sidenav: string, newValue: boolean) { UpdateSidenavStatus(sidenav: string, newValue: boolean) {

View File

@ -42,3 +42,9 @@ button {
.mat-column-actions { .mat-column-actions {
text-align: center; text-align: center;
} }
.add-new-shopkeeper-button {
float: right;
color: whitesmoke;
border-color: whitesmoke;
}

View File

@ -11,23 +11,19 @@
<td mat-cell *matCellDef="let row"> {{row.name}} </td> <td mat-cell *matCellDef="let row"> {{row.name}} </td>
</ng-container> </ng-container>
<ng-container matColumnDef="itemsCount">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Items Count </th>
<td mat-cell *matCellDef="let row"> {{row.itemsCount}} </td>
</ng-container>
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> Actions </th> <th mat-header-cell *matHeaderCellDef> Actions </th>
<td mat-cell *matCellDef="let row"> <td mat-cell *matCellDef="let row">
<button mat-flat-button <button mat-flat-button
*ngIf="row.isAvailable" *ngIf="row.isAvailable"
color="red"> color="red"
(click)="DeactivateShopkeeper(row.id)">
Deactivate Deactivate
</button> </button>
<button mat-flat-button *ngIf="!row.isAvailable" color="green"> <button mat-flat-button *ngIf="!isAnyAvailable" color="green" (click)="ActivateShopkeeper(row.id)">
Activate Activate
</button> </button>
<button mat-flat-button color="warn" > <button mat-flat-button color="warn" (click)="RemoveShopkeeper(row.id)">
Remove Remove
</button> </button>
@ -44,3 +40,7 @@
<mat-paginator [pageSizeOptions]="[5, 10, 25, 100]"></mat-paginator> <mat-paginator [pageSizeOptions]="[5, 10, 25, 100]"></mat-paginator>
</div> </div>
<button mat-stroked-button class="add-new-shopkeeper-button" (click)="AddNewShopkeeper()">
Add new
</button>

View File

@ -3,6 +3,13 @@ import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort'; import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { MonsterViewModel } from '../../../types/viewmodels/monster-viewmodels/MonsterViewModel'; import { MonsterViewModel } from '../../../types/viewmodels/monster-viewmodels/MonsterViewModel';
import { ShopkeeperViewModel } from '../../../types/viewmodels/shopkeeper-viewmodels/ShopkeeperViewModel';
import { ShopkeeperService } from '../../../services/shopkeeper.service';
import { first } from 'rxjs/operators';
import { ErrorResponse } from '../../../types/ErrorResponse';
import { HttpErrorResponse } from '@angular/common/http';
import { MatDialog } from '@angular/material';
import { NewShopkeeperDialogComponent } from './shopkeeper-dialogs/new-shopkeeper-dialog/new-shopkeeper-dialog.component';
@Component({ @Component({
selector: 'app-game-master-shopkeepers-table', selector: 'app-game-master-shopkeepers-table',
@ -10,35 +17,120 @@ import { MonsterViewModel } from '../../../types/viewmodels/monster-viewmodels/M
styleUrls: ['./game-master-shopkeepers-table.component.css'], styleUrls: ['./game-master-shopkeepers-table.component.css'],
}) })
export class GameMasterShopkeepersTableComponent implements OnInit { export class GameMasterShopkeepersTableComponent implements OnInit {
displayedColumns: string[] = ['name', 'itemsCount', 'actions']; displayedColumns: string[] = ['name', 'actions'];
dataSource: MatTableDataSource<{ dataSource: MatTableDataSource<ShopkeeperViewModel>;
name: string; isAnyAvailable: boolean = false;
itemsCount: number;
isAvailable: boolean;
}>; //TODO zmienić na skopkeeper view model
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@ViewChild(MatSort, { static: true }) sort: MatSort; @ViewChild(MatSort, { static: true }) sort: MatSort;
constructor() {} constructor(
private shopkeeperService: ShopkeeperService,
public dialog: MatDialog
) {}
ngOnInit() { ngOnInit() {
this.dataSource = new MatTableDataSource<{ this.GetAllShopkeepers();
name: string; }
itemsCount: number;
isAvailable: boolean; GetAllShopkeepers() {
}>([ this.shopkeeperService
{ .GetAllShopkeepers()
name: 'Test', .pipe(first())
itemsCount: 12, .subscribe(
isAvailable: true, (result) => {
this.dataSource = new MatTableDataSource(result);
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
if (result.find((e) => e.isAvailable) != null) {
this.isAnyAvailable = true;
}
}, },
{ (error: ErrorResponse | HttpErrorResponse) => {
name: 'Test2', console.error(error);
itemsCount: 12, if (error instanceof HttpErrorResponse) {
isAvailable: false, error = error.error as ErrorResponse;
}
console.error(error.message);
}
);
}
ActivateShopkeeper(shopkeeperId: number) {
this.shopkeeperService
.ChangeShopkeeperStatus(shopkeeperId, true)
.pipe(first())
.subscribe(
(result) => {
this.dataSource.data.find(
(e) => e.id == shopkeeperId
).isAvailable = true;
this.isAnyAvailable = true;
}, },
]); (error: ErrorResponse | HttpErrorResponse) => {
console.error(error);
if (error instanceof HttpErrorResponse) {
error = error.error as ErrorResponse;
}
console.error(error.message);
}
);
}
DeactivateShopkeeper(shopkeeperId: number) {
this.shopkeeperService
.ChangeShopkeeperStatus(shopkeeperId, false)
.pipe(first())
.subscribe(
(result) => {
this.dataSource.data.find(
(e) => e.id == shopkeeperId
).isAvailable = false;
this.isAnyAvailable = false;
},
(error: ErrorResponse | HttpErrorResponse) => {
console.error(error);
if (error instanceof HttpErrorResponse) {
error = error.error as ErrorResponse;
}
console.error(error.message);
}
);
}
RemoveShopkeeper(shopkeeperId: number) {
let currentStatus = this.dataSource.data.find((e) => e.id == shopkeeperId)
.isAvailable;
this.shopkeeperService
.RemoveShopkeeper(shopkeeperId, currentStatus)
.pipe(first())
.subscribe(
(result) => {
if (currentStatus == true) {
this.isAnyAvailable = false;
}
this.dataSource.data = this.dataSource.data.filter(
(e) => e.id != shopkeeperId
);
},
(error: ErrorResponse | HttpErrorResponse) => {
console.error(error);
if (error instanceof HttpErrorResponse) {
error = error.error as ErrorResponse;
}
console.error(error.message);
}
);
}
AddNewShopkeeper() {
this.dialog
.open(NewShopkeeperDialogComponent)
.afterClosed()
.pipe(first())
.subscribe(() => {
this.GetAllShopkeepers();
});
} }
applyFilter(event: Event) { applyFilter(event: Event) {

View File

@ -0,0 +1,62 @@
::ng-deep .mat-horizontal-content-container {
overflow-y: auto !important;
max-height: 500px;
}
table {
background-color: initial;
}
mat-paginator {
background-color: initial;
color: white;
}
::ng-deep .mat-select-arrow {
color: whitesmoke;
}
::ng-deep .mat-select-value {
color: white;
}
.button-to-right {
float: right;
}
.button-to-right > button {
color: whitesmoke;
}
td,
th {
color: whitesmoke;
}
.shopkeeper-create-button {
color: #7bce7b !important;
}
.shopkeeper-stepper-main {
background-color: #4f5c69;
}
::ng-deep .mat-step-label {
color: white !important;
}
::ng-deep .mat-step-icon-selected {
background-color: #df7c0f !important;
}
::ng-deep .mat-step-icon-state-edit {
background-color: #df7c0f !important;
}
::ng-deep mat-dialog-container {
background-color: #4f5c69 !important;
}
::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background {
background-color: #df7c0f;
}

View File

@ -0,0 +1,104 @@
<mat-horizontal-stepper [linear]="true" #stepper class="shopkeeper-stepper-main">
<mat-step [stepControl]="nameFormGroup">
<form [formGroup]="nameFormGroup">
<ng-template matStepLabel class="stepper-header">Fill out your shop name</ng-template>
<mat-form-field>
<mat-label>Name</mat-label>
<input matInput placeholder="Bob..." formControlName="name" required>
</mat-form-field>
</form>
<div class="button-to-right">
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step>
<ng-template matStepLabel>Select items</ng-template>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSourceWithAllitems" matSort class="w-100">
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef> Select
</th>
<td mat-cell *matCellDef="let row">
<mat-checkbox (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="itemName">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
<td mat-cell *matCellDef="let row"> {{row.itemName}} </td>
</ng-container>
<ng-container matColumnDef="itemType">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Type </th>
<td mat-cell *matCellDef="let row"> {{row.itemType}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr class="mat-row" *matNoDataRow>
<td class="mat-cell" colspan="4">No data found</td>
</tr>
</table>
<mat-paginator [pageSizeOptions]="[5, 10, 25, 100]"></mat-paginator>
</div>
<div class="button-to-right">
<button mat-button matStepperPrevious>Back</button>
<button mat-button (click)="ToFinalStep(stepper)" >Next</button>
</div>
</mat-step>
<mat-step>
<ng-template matStepLabel>Set a quantity</ng-template>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="finalDataSource" matSort class="w-100">
<ng-container matColumnDef="itemName">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
<td mat-cell *matCellDef="let row"> {{row.itemName}} </td>
</ng-container>
<ng-container matColumnDef="itemType">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Type </th>
<td mat-cell *matCellDef="let row"> {{row.itemType}} </td>
</ng-container>
<ng-container matColumnDef="amount" >
<th mat-header-cell *matHeaderCellDef mat-sort-header > Amount </th>
<td mat-cell *matCellDef="let row">
<mat-form-field style="width: 60px">
<input matInput type="number" [(ngModel)]="row.amount" min="1">
</mat-form-field>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Remove </th>
<td mat-cell *matCellDef="let row">
<button mat-icon-button (click)="RemoveItem(row.weaponId, row.armorId)">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumnsForFinal"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumnsForFinal;"></tr>
<tr class="mat-row" *matNoDataRow>
<td class="mat-cell" colspan="4">No data found</td>
</tr>
</table>
<mat-paginator [pageSizeOptions]="[5, 10, 25, 100]"></mat-paginator>
</div>
<div class="button-to-right">
<button mat-button matStepperPrevious>Back</button>
<button mat-button (click)="CreateShopkeeper()" class="shopkeeper-create-button">Create</button>
</div>
</mat-step>
</mat-horizontal-stepper>

View File

@ -0,0 +1,170 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { WeaponService } from '../../../../../services/weapon.service';
import { ArmorService } from '../../../../../services/armor.service';
import { ShopkeeperItemsWithItemNameAndTypeViewModel } from '../../../../../types/viewmodels/shopkeeper-viewmodels/ShopkeeperItemsWithItemNameAndTypeViewModel';
import { first, map } from 'rxjs/operators';
import { forkJoin, Observable } from 'rxjs';
import { SelectionModel } from '@angular/cdk/collections';
import {
MatDialog,
MatDialogRef,
MatHorizontalStepper,
} from '@angular/material';
import { ShopkeeperService } from '../../../../../services/shopkeeper.service';
import { ShopkeeperWithItemsViewModel } from '../../../../../types/viewmodels/shopkeeper-viewmodels/ShopkeeperWithItemsViewModel';
import { ShopkeeperItemsViewModel } from '../../../../../types/viewmodels/shopkeeper-viewmodels/ShopkeeperItemsViewModel';
@Component({
selector: 'app-new-shopkeeper-dialog',
templateUrl: './new-shopkeeper-dialog.component.html',
styleUrls: ['./new-shopkeeper-dialog.component.css'],
})
export class NewShopkeeperDialogComponent implements OnInit {
displayedColumnsForFinal: string[] = [
'itemName',
'itemType',
'amount',
'actions',
];
displayedColumns: string[] = ['select', 'itemName', 'itemType'];
dataSourceWithAllitems: MatTableDataSource<ShopkeeperItemsWithItemNameAndTypeViewModel>;
selection = new SelectionModel<ShopkeeperItemsWithItemNameAndTypeViewModel>(
true,
[]
);
finalDataSource: MatTableDataSource<ShopkeeperItemsWithItemNameAndTypeViewModel>;
nameFormGroup: FormGroup;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@ViewChild(MatSort, { static: true }) sort: MatSort;
@ViewChild(MatPaginator, { static: true }) paginatorForFinal: MatPaginator;
@ViewChild(MatSort, { static: true }) sortForFinal: MatSort;
constructor(
private formBuilder: FormBuilder,
private weaponService: WeaponService,
private armorService: ArmorService,
private shopkeeperService: ShopkeeperService,
public dialogRef: MatDialogRef<NewShopkeeperDialogComponent>
) {}
ngOnInit() {
this.GetAllWeaponsAndArmors()
.pipe(first())
.subscribe((res) => {
this.dataSourceWithAllitems = new MatTableDataSource<ShopkeeperItemsWithItemNameAndTypeViewModel>(
res
);
this.dataSourceWithAllitems.sort = this.sort;
this.dataSourceWithAllitems.paginator = this.paginator;
});
this.finalDataSource = new MatTableDataSource<ShopkeeperItemsWithItemNameAndTypeViewModel>(
[]
);
this.finalDataSource.paginator = this.paginatorForFinal;
this.finalDataSource.sort = this.sortForFinal;
this.nameFormGroup = this.formBuilder.group({
name: ['', Validators.required],
});
}
GetAllWeaponsAndArmors(): Observable<
ShopkeeperItemsWithItemNameAndTypeViewModel[]
> {
return forkJoin([
this.armorService.GetAllArmors(),
this.weaponService.GetAllWeapons(),
]).pipe(
first(),
map(([armors, weapons]) => {
let resultArray: ShopkeeperItemsWithItemNameAndTypeViewModel[];
resultArray = weapons.map<ShopkeeperItemsWithItemNameAndTypeViewModel>(
(e) => {
return {
shopkeeperId: null,
amount: 1,
armorId: null,
weaponId: e.id,
itemName: e.name,
itemType: 'Weapon',
};
}
);
return [
...resultArray,
...armors.map<ShopkeeperItemsWithItemNameAndTypeViewModel>((e) => {
return {
shopkeeperId: null,
amount: 1,
armorId: null,
weaponId: e.id,
itemName: e.name,
itemType: 'Armor',
};
}),
];
})
);
}
ToFinalStep(stepper: MatHorizontalStepper) {
this.selection.selected.forEach((item) => {
if (
this.finalDataSource.data.filter(
(e) => e.armorId == item.armorId && e.weaponId == item.weaponId
).length == 0
) {
this.finalDataSource.data = [...this.finalDataSource.data, item];
}
});
this.selection.clear();
stepper.next();
}
CreateShopkeeper() {
if (this.finalDataSource.data.length > 0) {
let filteredData = this.finalDataSource.data.filter((e) => e.amount > 0);
let shopkeeperModel: ShopkeeperWithItemsViewModel = {
id: null,
name: this.nameFormGroup.value.name,
isAvailable: false,
items: filteredData.map<ShopkeeperItemsViewModel>((item) => {
return {
shopkeeperId: null,
armorId: item.armorId,
weaponId: item.weaponId,
amount: item.amount,
};
}),
};
this.shopkeeperService
.CreateShopkeeper(shopkeeperModel)
.pipe(first())
.subscribe((result) => {
this.dialogRef.close();
});
}
}
RemoveItem(weaponId: number, armorId: number) {
if (weaponId != null) {
this.finalDataSource.data = this.finalDataSource.data.filter(
(e) => e.weaponId != weaponId
);
} else if (armorId != null) {
this.finalDataSource.data = this.finalDataSource.data.filter(
(e) => e.armorId != armorId
);
}
}
}

View File

@ -116,7 +116,6 @@ export class GameMasterTurntrackerComponent implements OnInit, OnDestroy {
); );
result.forEach((ele) => { result.forEach((ele) => {
if (monstersOnList.map((e) => e.name).includes(ele.name)) { if (monstersOnList.map((e) => e.name).includes(ele.name)) {
debugger;
let maxCurrentId = Math.max( let maxCurrentId = Math.max(
...monstersOnList.map((e) => e.monsterId), ...monstersOnList.map((e) => e.monsterId),
0 0

View File

@ -1,9 +1,16 @@
@import "../../../styles.css"; @import '../../../styles.css';
.toggle-class { .toggle-class {
margin: 5px; margin: 5px;
} }
.character-name {
width: 240px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.menu-spacer { .menu-spacer {
flex: 1 1 auto; flex: 1 1 auto;
} }

View File

@ -5,8 +5,8 @@
<mat-icon>menu</mat-icon> <mat-icon>menu</mat-icon>
</button> </button>
<span class="menu-spacer"></span> <span class="menu-spacer"></span>
<div fxShow="true" fxHide.lt-md> <div fxShow="true" fxHide.lt-md class="character-name">
<span>Session Companion</span> <span>{{characterName}}</span>
</div> </div>
</mat-toolbar-row> </mat-toolbar-row>
</mat-toolbar> </mat-toolbar>
@ -47,7 +47,7 @@
<a matLine>Profile</a> <a matLine>Profile</a>
</mat-list-item>--> </mat-list-item>-->
<mat-list-item> <mat-list-item (click)="SwitchMiddleComponent('PlayerShopComponent')">
<mat-icon [class.active]="selected" matListIcon>shopping_cart</mat-icon> <mat-icon [class.active]="selected" matListIcon>shopping_cart</mat-icon>
<a matLine>Shop</a> <a matLine>Shop</a>
</mat-list-item> </mat-list-item>

View File

@ -12,6 +12,8 @@ import { Router } from '@angular/router';
import { ClearCharacterId } from '../../store/actions/player.action'; import { ClearCharacterId } from '../../store/actions/player.action';
import { MatSnackBar } from '@angular/material'; import { MatSnackBar } from '@angular/material';
import { SnackbarComponent } from '../../shared/snackbar/snackbar.component'; import { SnackbarComponent } from '../../shared/snackbar/snackbar.component';
import { CharacterService } from '../../../services/character.service';
import { PlayerShopComponent } from '../player-shop/player-shop.component';
@Component({ @Component({
selector: 'app-player-dashboard', selector: 'app-player-dashboard',
@ -22,12 +24,14 @@ export class PlayerDashboardComponent implements OnInit {
middleComponent; middleComponent;
isExpanded = false; isExpanded = false;
selected = false; selected = false;
characterName: string = '';
constructor( constructor(
private signalRService: PlayerSignalRService, private signalRService: PlayerSignalRService,
private store: Store<AppState>, private store: Store<AppState>,
private router: Router, private router: Router,
private _snackBar: MatSnackBar private _snackBar: MatSnackBar,
private characterService: CharacterService
) {} ) {}
ngOnInit() { ngOnInit() {
@ -38,6 +42,12 @@ export class PlayerDashboardComponent implements OnInit {
this.SubscribeToEvents(); this.SubscribeToEvents();
this.signalRService.Login(id); this.signalRService.Login(id);
this.SwitchMiddleComponent('AbilitiesComponent'); this.SwitchMiddleComponent('AbilitiesComponent');
this.characterService
.GetCharacterBasicInfo(id)
.pipe(first())
.subscribe((result) => {
this.characterName = result.name;
});
}); });
} }
@ -59,6 +69,9 @@ export class PlayerDashboardComponent implements OnInit {
case 'PlayerOtherEquipmentTableComponent': case 'PlayerOtherEquipmentTableComponent':
this.middleComponent = PlayerOtherEquipmentTableComponent; this.middleComponent = PlayerOtherEquipmentTableComponent;
break; break;
case 'PlayerShopComponent':
this.middleComponent = PlayerShopComponent;
break;
} }
} }
@ -82,6 +95,12 @@ export class PlayerDashboardComponent implements OnInit {
gmMessage: result.parameters.message, gmMessage: result.parameters.message,
}, },
}); });
break;
case 'RefreshShopComponent':
this.middleComponent = '';
this.middleComponent = PlayerShopComponent;
break;
} }
} }
); );

View File

@ -0,0 +1,60 @@
.no-shops-button {
color: whitesmoke !important;
background-color: #ad2424 !important;
}
table {
background-color: initial;
}
mat-paginator {
background-color: initial;
color: white;
}
::ng-deep .mat-select-arrow {
color: whitesmoke;
}
::ng-deep .mat-select-value {
color: white;
}
.mat-sort-header-container {
color: whitesmoke !important;
}
.mat-form-field {
font-size: 14px;
width: 100%;
}
td,
th {
color: whitesmoke;
}
.mat-header-cell {
flex-direction: column;
justify-content: center;
text-align: center;
}
.mat-cell {
text-align: center;
justify-content: center;
}
::ng-deep td.mat-cell:first-of-type,
::ng-deep th.mat-header-cell:first-of-type {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100px;
}
.player-shop-buy-button {
background-color: #1f4416;
margin-left: 5px;
color: whitesmoke;
}

View File

@ -0,0 +1,70 @@
<div *ngIf="shopkeeper == null">
<button mat-flat-button disabled class="no-shops-button">
All stores are currently closed, please visit us later.
</button>
</div>
<div *ngIf="shopkeeper != null">
<p>
{{shopkeeper.name}}
</p>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort class="w-100">
<ng-container matColumnDef="itemName" >
<th mat-header-cell *matHeaderCellDef mat-sort-header > Name </th>
<td mat-cell *matCellDef="let row">
<span *ngIf="row.armor != null; else weaponNameBlock" >
{{row.armor.name}}
</span>
<ng-template #weaponNameBlock >
{{row.weapon.name}}
</ng-template>
</td>
</ng-container>
<ng-container matColumnDef="itemType">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Type </th>
<td mat-cell *matCellDef="let row">
<span *ngIf="row.armor != null; else weaponTypeBlock" >
Armor
</span>
<ng-template #weaponTypeBlock >
Weapon
</ng-template>
</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Amount </th>
<td mat-cell *matCellDef="let row"> {{row.amount}} </td>
</ng-container>
<ng-container matColumnDef="amountToBuy">
<th mat-header-cell *matHeaderCellDef mat-sort-header> How many </th>
<td mat-cell *matCellDef="let row">
<mat-form-field style="width: 60px">
<input matInput type="number" [(ngModel)]="row.amountToBuy" min="1">
</mat-form-field>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Actions </th>
<td mat-cell *matCellDef="let row">
<button class="player-shop-buy-button" mat-flat-button (click)="BuyItem(row.weaponId, row.armorId)">
Buy
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr class="mat-row" *matNoDataRow>
<td class="mat-cell" colspan="4">No data found</td>
</tr>
</table>
<mat-paginator [pageSizeOptions]="[5, 10, 25, 100]"></mat-paginator>
</div>
</div>

View File

@ -0,0 +1,144 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { ShopkeeperWithItemsDetailsViewModel } from '../../../types/viewmodels/shopkeeper-viewmodels/ShopkeeperWithItemsDetailsViewModel';
import { ShopkeeperService } from '../../../services/shopkeeper.service';
import { first } from 'rxjs/operators';
import { PlayerSignalRService } from '../../shared/signalR-service/player-signalR.service';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { ShopkeeperItemsWithItemNameAndTypeForPlayerViewModel } from '../../../types/viewmodels/shopkeeper-viewmodels/ShopkeeperItemsWithItemNameAndTypeForPlayerViewModel';
import { Store } from '@ngrx/store';
import { AppState } from '../../store/models/app-state.model';
@Component({
selector: 'app-player-shop',
templateUrl: './player-shop.component.html',
styleUrls: ['./player-shop.component.css'],
})
export class PlayerShopComponent implements OnInit {
shopkeeper: ShopkeeperWithItemsDetailsViewModel;
displayedColumns: string[] = [
'itemName',
'itemType',
'amount',
'amountToBuy',
'actions',
];
dataSource: MatTableDataSource<ShopkeeperItemsWithItemNameAndTypeForPlayerViewModel>;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@ViewChild(MatSort, { static: true }) sort: MatSort;
constructor(
private shopkeeperService: ShopkeeperService,
private signalRService: PlayerSignalRService,
private store: Store<AppState>
) {}
ngOnInit() {
this.SubscribeToEvents();
this.GetActiveShopkeeper();
}
GetActiveShopkeeper(): void {
this.shopkeeperService
.GetActiveShopkeeper()
.pipe(first())
.subscribe((result) => {
this.shopkeeper = result;
if (this.shopkeeper != null) {
this.dataSource = new MatTableDataSource(
result.items.map<ShopkeeperItemsWithItemNameAndTypeForPlayerViewModel>(
(item) => {
return {
shopkeeperId: item.shopkeeperId,
weaponId: item.weaponId,
armorId: item.armorId,
amount: item.amount,
amountToBuy: 1,
armor: item.armor,
weapon: item.weapon,
};
}
)
);
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
}
});
}
BuyItem(weaponId: number, armorId: number) {
this.store
.select((s) => s.playerStore.characterId)
.pipe(first())
.subscribe((chracterId) => {
let amount = this.dataSource.data.find(
(item) => item.weaponId == weaponId && item.armorId == armorId
).amountToBuy;
this.shopkeeperService
.BuyItemFromShopkeeper(
this.shopkeeper.id,
chracterId,
weaponId,
armorId,
amount
)
.pipe(first())
.subscribe(
(result) => {},
(error) => {
console.error(error);
}
);
});
}
RefreshAmountOnList() {
this.shopkeeperService
.GetActiveShopkeeper()
.pipe(first())
.subscribe((result) => {
this.dataSource.data.forEach((item, index, object) => {
if (item.weaponId != null) {
let lineWithSameWeapon = result.items.find(
(e) => e.weaponId == item.weaponId
);
if (lineWithSameWeapon == null) {
object.splice(index, 1);
} else {
object[index].amount = lineWithSameWeapon.amount;
}
} else if (item.armorId != null) {
let lineWithSameArmor = result.items.find(
(e) => e.armorId == item.armorId
);
if (lineWithSameArmor == null) {
object.splice(index, 1);
} else {
object[index].amount = lineWithSameArmor.amount;
}
}
});
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
});
}
private SubscribeToEvents(): void {
this.signalRService.runMethod.subscribe(
(result: { methodName: string; parameters }) => {
switch (result.methodName) {
case 'RefreshShopComponent':
this.GetActiveShopkeeper();
break;
case 'RefreshShopkeeperItems':
this.RefreshAmountOnList();
break;
}
}
);
}
}

View File

@ -33,5 +33,29 @@ export class PlayerSignalRService {
}); });
} }
); );
this.signalR.hubConnection.on('NewShopkeeperArrived', () => {
this.runMethod.next({
methodName: 'RefreshShopComponent',
parameters: {},
});
});
this.signalR.hubConnection.on('ShopkeeperLeft', () => {
this.runMethod.next({
methodName: 'RefreshShopComponent',
parameters: {},
});
});
this.signalR.hubConnection.on('ShopkeeperRemoved', () => {
this.runMethod.next({
methodName: 'RefreshShopComponent',
parameters: {},
});
});
this.signalR.hubConnection.on('ItemFromShopkeeperWasBought', () => {
this.runMethod.next({
methodName: 'RefreshShopkeeperItems',
parameters: {},
});
});
} }
} }

View File

@ -9,6 +9,7 @@ import { LoggedCharactersViewModel } from '../types/viewmodels/character-viewmod
import { CharacterStatsViewModel } from '../types/viewmodels/character-viewmodels/CharacterStatsViewModel'; import { CharacterStatsViewModel } from '../types/viewmodels/character-viewmodels/CharacterStatsViewModel';
import { CharacterFromTemplatesViewModel } from '../types/viewmodels/character-viewmodels/CharacterFromTemplatesViewModel'; import { CharacterFromTemplatesViewModel } from '../types/viewmodels/character-viewmodels/CharacterFromTemplatesViewModel';
import { SuccessResponse } from '../types/SuccessResponse'; import { SuccessResponse } from '../types/SuccessResponse';
import { CharacterBasicInfoViewModel } from '../types/viewmodels/character-viewmodels/CharacterBasicInfoViewModel';
Injectable({ Injectable({
providedIn: 'root', providedIn: 'root',
@ -117,4 +118,24 @@ export class CharacterService {
}) })
); );
} }
GetCharacterBasicInfo(
characterId: number
): Observable<CharacterBasicInfoViewModel> {
const params = new HttpParams().set('characterId', characterId.toString());
return this.http
.get<Either<CharacterBasicInfoViewModel, ErrorResponse>>(
this.baseUrl + 'characterBasicInfo',
{ params }
)
.pipe(
switchMap((response) => {
if (response.isLeft) {
return of(response.left);
} else {
return throwError(response.right);
}
})
);
}
} }

View File

@ -0,0 +1,167 @@
import { Inject, Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { ShopkeeperViewModel } from '../types/viewmodels/shopkeeper-viewmodels/ShopkeeperViewModel';
import { Either } from '../types/Either';
import { ErrorResponse } from '../types/ErrorResponse';
import { switchMap } from 'rxjs/operators';
import { SuccessResponse } from '../types/SuccessResponse';
import { ShopkeeperWithItemsDetailsViewModel } from '../types/viewmodels/shopkeeper-viewmodels/ShopkeeperWithItemsDetailsViewModel';
import { ShopkeeperWithItemsViewModel } from '../types/viewmodels/shopkeeper-viewmodels/ShopkeeperWithItemsViewModel';
Injectable({
providedIn: 'root',
});
export class ShopkeeperService {
private baseUrl = 'api/shopkeeper/';
constructor(private http: HttpClient, @Inject('BASE_URL') baseUrl: string) {
this.baseUrl = baseUrl + this.baseUrl;
}
GetAllShopkeepers(): Observable<ShopkeeperViewModel[]> {
return this.http
.get<Either<ShopkeeperViewModel[], ErrorResponse>>(
this.baseUrl + 'getShopkeepers'
)
.pipe(
switchMap((response) => {
if (response.isLeft) {
return of(response.left);
} else {
return throwError(response.right);
}
})
);
}
GetActiveShopkeeper(): Observable<ShopkeeperWithItemsDetailsViewModel> {
return this.http
.get<Either<ShopkeeperWithItemsDetailsViewModel, ErrorResponse>>(
this.baseUrl + 'getActiveShopkeeper'
)
.pipe(
switchMap((response) => {
if (response.isLeft) {
return of(response.left);
} else {
return throwError(response.right);
}
})
);
}
CreateShopkeeper(
shopkeeperModel: ShopkeeperWithItemsViewModel
): Observable<SuccessResponse> {
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
};
return this.http
.post<Either<SuccessResponse, ErrorResponse>>(
this.baseUrl + 'createNewShopkeeper',
shopkeeperModel,
httpOptions
)
.pipe(
switchMap((response) => {
if (response.isLeft) {
return of(response.left);
} else {
return throwError(response.right);
}
})
);
}
ChangeShopkeeperStatus(
shopkeeperId: number,
newStatus: boolean
): Observable<SuccessResponse> {
const params = new HttpParams()
.set('shopkeeperId', shopkeeperId.toString())
.set('availability', newStatus.toString());
return this.http
.put<Either<SuccessResponse, ErrorResponse>>(
this.baseUrl + 'changeShopkeeperStatus',
null,
{ params }
)
.pipe(
switchMap((response) => {
if (response.isLeft) {
return of(response.left);
} else {
return throwError(response.right);
}
})
);
}
RemoveShopkeeper(
shopkeeperId: number,
currentStatus: boolean
): Observable<SuccessResponse> {
const params = new HttpParams()
.set('shopkeeperId', shopkeeperId.toString())
.set('wasAvailable', currentStatus.toString());
return this.http
.delete<Either<SuccessResponse, ErrorResponse>>(
this.baseUrl + 'removeShopkeeper',
{ params }
)
.pipe(
switchMap((response) => {
if (response.isLeft) {
return of(response.left);
} else {
return throwError(response.right);
}
})
);
}
BuyItemFromShopkeeper(
shopkeeperId: number,
characterId: number,
weaponId: number,
armorId: number,
amount: number
): Observable<SuccessResponse> {
let params = new HttpParams();
if (armorId != null) {
params = new HttpParams()
.set('shopkeeperId', shopkeeperId.toString())
.set('characterId', characterId.toString())
.set('amount', amount.toString())
.set('armorId', armorId.toString());
}
if (weaponId != null) {
params = new HttpParams()
.set('shopkeeperId', shopkeeperId.toString())
.set('characterId', characterId.toString())
.set('amount', amount.toString())
.set('weaponId', weaponId.toString());
}
return this.http
.put<Either<SuccessResponse, ErrorResponse>>(
this.baseUrl + 'buyItem',
null,
{ params }
)
.pipe(
switchMap((response) => {
if (response.isLeft) {
return of(response.left);
} else {
return throwError(response.right);
}
})
);
}
}

View File

@ -0,0 +1,9 @@
export interface CharacterBasicInfoViewModel {
id: number;
name: string;
level: number;
currentHealthPoints: number;
maxHealthPoints: number;
class: string;
race: string;
}

View File

@ -0,0 +1,6 @@
export interface ShopkeeperItemsViewModel {
shopkeeperId: number;
armorId: number;
weaponId: number;
amount: number;
}

View File

@ -0,0 +1,9 @@
export interface ShopkeeperItemsWithItemNameAndTypeForPlayerViewModel {
shopkeeperId: number;
armorId: number;
weaponId: number;
amount: number;
amountToBuy: number;
armor: {};
weapon: {};
}

View File

@ -0,0 +1,8 @@
export interface ShopkeeperItemsWithItemNameAndTypeViewModel {
shopkeeperId: number;
armorId: number;
weaponId: number;
amount: number;
itemName: string;
itemType: string;
}

View File

@ -0,0 +1,5 @@
export interface ShopkeeperViewModel {
id: number;
name: string;
isAvailable: boolean;
}

View File

@ -0,0 +1,43 @@
export interface ShopkeeperWithItemsDetailsViewModel {
id: number;
name: string;
isAvailable: boolean;
items: [
{
shopkeeperId: number;
armorId: number;
armor: {
id: number;
name: string;
category: string;
armorClassBase: string;
haveDexterityBonus: true;
minimumStrength: number;
haveStealthDisadvantage: boolean;
weight: number;
cost: number;
currencyType: number;
};
weaponId: number;
weapon: {
id: number;
name: string;
cost: number;
weight: number;
currencyType: number;
diceCount: number;
diceValue: number;
twoHandDiceCount: number;
twoHandDiceValue: number;
twoHandDamageType: string;
description: string;
weaponType: string;
rangeMeele: number;
rangeThrowNormal: number;
rangeThrowLong: number;
rangeLong: number;
};
amount: number;
}
];
}

View File

@ -0,0 +1,8 @@
import { ShopkeeperItemsViewModel } from './ShopkeeperItemsViewModel';
export interface ShopkeeperWithItemsViewModel {
id: number;
name: string;
isAvailable: boolean;
items: ShopkeeperItemsViewModel[];
}

View File

@ -11,26 +11,54 @@ using System.Threading.Tasks;
namespace SessionCompanion.Controllers namespace SessionCompanion.Controllers
{ {
using Microsoft.AspNetCore.SignalR;
using SessionCompanion.Hubs;
using SessionCompanion.ViewModels.CharacterArmorViewModels;
using SessionCompanion.ViewModels.CharacterWeaponViewModels;
[ApiController] [ApiController]
[Route("api/shopkeeper")] [Route("api/shopkeeper")]
public class ShopkeeperController : Controller public class ShopkeeperController : Controller
{ {
private IShopkeeperService _service; private IShopkeeperService _service;
public ShopkeeperController(IShopkeeperService shopkeeperService)
private IShopkeeperItemService _shopkeeperItemsService;
private ICharacterArmorService _characterArmorService;
private ICharacterWeaponService _characterWeaponService;
private IHubContext<SessionHub> _sessionHub;
public ShopkeeperController(IShopkeeperService shopkeeperService, IShopkeeperItemService shopkeeperItemsService, ICharacterWeaponService characterWeaponService, ICharacterArmorService characterArmorService, IHubContext<SessionHub> sessionHub)
{ {
_service = shopkeeperService; this._service = shopkeeperService;
this._shopkeeperItemsService = shopkeeperItemsService;
this._characterArmorService = characterArmorService;
this._characterWeaponService = characterWeaponService;
this._sessionHub = sessionHub;
} }
/// <summary> /// <summary>
/// Endpoint zwracający liste sklepikarzy /// Endpoint zwracający liste sklepikarzy
/// </summary> /// </summary>
/// <returns>Lista sklepikarzy</returns> /// <returns>Lista sklepikarzy</returns>
[HttpGet("getShopkeepers")] [HttpGet("getShopkeepers")]
public async Task<List<ShopkeeperViewModel>> GetShopkeepers() public async Task<Either<List<ShopkeeperViewModel>, ErrorResponse>> GetShopkeepers()
{ {
return _service.Get().ToList(); return _service.Get().ToList();
} }
/// <summary>
/// Endpoint zwracający aktywnego sklepikarza
/// </summary>
/// <returns>Lista sklepikarzy</returns>
[HttpGet("getActiveShopkeeper")]
public async Task<Either<ShopkeeperWithItemsDetailsViewModel, ErrorResponse>> GetActiveShopkeepers()
{
return await _service.GetActiveShkopkeeperWithItems();
}
/// <summary> /// <summary>
/// Endpoint służacy do zmiany statusu sklepikarza /// Endpoint służacy do zmiany statusu sklepikarza
/// </summary> /// </summary>
@ -38,9 +66,118 @@ namespace SessionCompanion.Controllers
/// <param name="availability"></param> /// <param name="availability"></param>
/// <returns>SuccesResponse/ErrorResponse</returns> /// <returns>SuccesResponse/ErrorResponse</returns>
[HttpPut("changeShopkeeperStatus")] [HttpPut("changeShopkeeperStatus")]
public async Task<Either<SuccessResponse, ErrorResponse>> ChangeShopkeeperStatus([Required] int shopkeeperId, [Required] bool availability) public async Task<Either<SuccessResponse, ErrorResponse>> ChangeShopkeeperStatus([FromQuery][Required] int shopkeeperId, [FromQuery][Required] bool availability)
{ {
return await _service.ChangeShopkeeperStatus(shopkeeperId, availability); var result = await _service.ChangeShopkeeperStatus(shopkeeperId, availability);
if (result.IsLeft && availability)
{
await this._sessionHub.Clients.Group("Players").SendAsync("NewShopkeeperArrived");
}
else
{
await this._sessionHub.Clients.Group("Players").SendAsync("ShopkeeperLeft");
}
return result;
}
/// <summary>
/// Metoda kupna przedmiotu od Shopkeepera
/// </summary>
/// <param name="shopkeeperId">id sprzedawcy</param>
/// <param name="characterId">id postaci</param>
/// <param name="weaponId">id broni</param>
/// <param name="armorId">id pancerza</param>
/// <param name="amount">ilosc przedmiotu do kupienia</param>
/// <returns>Sukces bądź błąd</returns>
[HttpPut("buyItem")]
public async Task<Either<SuccessResponse, ErrorResponse>> BuyItemFromShopkeeper([Required] int shopkeeperId, [Required] int characterId, int? weaponId, int? armorId, [Required] int amount)
{
if (amount <= 0)
{
return new ErrorResponse("Given amount is 0 or less");
}
if (shopkeeperId <= 0)
{
return new ErrorResponse("Wrong Shopkeeper Id");
}
if (characterId <= 0)
{
return new ErrorResponse("Wrong Character Id");
}
var substrackResult = await this._shopkeeperItemsService.GetActiveShkopkeeperWithItems(shopkeeperId, amount, weaponId, armorId);
if (substrackResult.IsLeft)
{
await this._sessionHub.Clients.Groups("Players").SendAsync("ItemFromShopkeeperWasBought");
if (weaponId.HasValue)
{
for (int i = 0; i < amount; i++)
{
await this._characterWeaponService.Create(
new CharacterWeaponViewModel()
{
CharacterId = characterId,
HoldInLeftHand = false,
HoldInRightHand = false,
InUse = false,
WeaponId = weaponId.Value
});
await this._characterWeaponService.SaveAsync();
}
}
if (armorId.HasValue)
{
for (int i = 0; i < amount; i++)
{
await this._characterArmorService.Create(
new CharacterArmorViewModel()
{
ArmorId = armorId.Value,
CharacterId = characterId,
InUse = false
});
await this._characterArmorService.SaveAsync();
}
}
return new SuccessResponse("Ttems have been purchased");
}
else
{
return substrackResult;
}
}
[HttpPost("createNewShopkeeper")]
public async Task<Either<SuccessResponse, ErrorResponse>> CreateNewShopKeeper([Required] ShopkeeperWithItemsViewModel shopkeeperWithItemsViewModel)
{
if (!ModelState.IsValid)
{
return new ErrorResponse() { StatusCode = 500, Message = "Model is invalid" };
}
return await _service.CreateNewShopKeeper(shopkeeperWithItemsViewModel);
}
[HttpDelete("removeShopkeeper")]
public async Task<Either<SuccessResponse, ErrorResponse>> RemoveShopkeeper([Required] int shopkeeperId, bool wasAvailable)
{
await this._service.Delete(shopkeeperId);
await this._service.SaveAsync();
if (wasAvailable)
{
await this._sessionHub.Clients.Group("Players").SendAsync("ShopkeeperRemoved");
}
return new SuccessResponse("Delete completed");
} }
} }
} }