diff --git a/SessionCompanion/SessionCompanion.Services/Interfaces/IShopkeeperItemService.cs b/SessionCompanion/SessionCompanion.Services/Interfaces/IShopkeeperItemService.cs index 6f6bb45..197522e 100644 --- a/SessionCompanion/SessionCompanion.Services/Interfaces/IShopkeeperItemService.cs +++ b/SessionCompanion/SessionCompanion.Services/Interfaces/IShopkeeperItemService.cs @@ -9,7 +9,11 @@ using System.Threading.Tasks; namespace SessionCompanion.Services.Interfaces { + using SessionCompanion.Extensions.EitherType; + using SessionCompanion.ViewModels.ApiResponses; + public interface IShopkeeperItemService : IServiceBase { + Task> GetActiveShkopkeeperWithItems(int shopkeeperId, int amount, int? weaponId, int? armorId); } } diff --git a/SessionCompanion/SessionCompanion.Services/Interfaces/IShopkeeperService.cs b/SessionCompanion/SessionCompanion.Services/Interfaces/IShopkeeperService.cs index 98f974c..9043682 100644 --- a/SessionCompanion/SessionCompanion.Services/Interfaces/IShopkeeperService.cs +++ b/SessionCompanion/SessionCompanion.Services/Interfaces/IShopkeeperService.cs @@ -15,5 +15,6 @@ namespace SessionCompanion.Services.Interfaces { Task> CreateNewShopKeeper(ShopkeeperWithItemsViewModel shopkeeperWithItemsViewModel); Task> ChangeShopkeeperStatus(int shopkeeperId, bool availability); + Task> GetActiveShkopkeeperWithItems(); } } diff --git a/SessionCompanion/SessionCompanion.Services/Profiles/ShopkeeperItemsProfile.cs b/SessionCompanion/SessionCompanion.Services/Profiles/ShopkeeperItemsProfile.cs index 5897825..ebf0f75 100644 --- a/SessionCompanion/SessionCompanion.Services/Profiles/ShopkeeperItemsProfile.cs +++ b/SessionCompanion/SessionCompanion.Services/Profiles/ShopkeeperItemsProfile.cs @@ -9,11 +9,17 @@ using System.Threading.Tasks; namespace SessionCompanion.Services.Profiles { + using SessionCompanion.ViewModels.ShopkeeperItemViewModels; + public class ShopkeeperItemsProfile : Profile { public ShopkeeperItemsProfile() { CreateMap().ReverseMap(); + + CreateMap() + .ForMember(vm => vm.Armor, conf => conf.MapFrom(item => item.Armor)) + .ForMember(vm => vm.Weapon, conf => conf.MapFrom(item => item.Weapon)).ReverseMap(); } } } diff --git a/SessionCompanion/SessionCompanion.Services/Profiles/ShopkeeperProfile.cs b/SessionCompanion/SessionCompanion.Services/Profiles/ShopkeeperProfile.cs index b09f4e0..011a41d 100644 --- a/SessionCompanion/SessionCompanion.Services/Profiles/ShopkeeperProfile.cs +++ b/SessionCompanion/SessionCompanion.Services/Profiles/ShopkeeperProfile.cs @@ -16,6 +16,9 @@ namespace SessionCompanion.Services.Profiles CreateMap().ReverseMap(); CreateMap() .ForMember(vm => vm.Items, conf => conf.MapFrom(items => items.ShopkeeperItems)).ReverseMap(); + + CreateMap() + .ForMember(vm => vm.Items, conf => conf.MapFrom(items => items.ShopkeeperItems)).ReverseMap(); } } } diff --git a/SessionCompanion/SessionCompanion.Services/Services/ShopkeeperItemService.cs b/SessionCompanion/SessionCompanion.Services/Services/ShopkeeperItemService.cs index 271e927..1778f59 100644 --- a/SessionCompanion/SessionCompanion.Services/Services/ShopkeeperItemService.cs +++ b/SessionCompanion/SessionCompanion.Services/Services/ShopkeeperItemService.cs @@ -5,16 +5,56 @@ using SessionCompanion.Services.Base; using SessionCompanion.Services.Interfaces; using SessionCompanion.ViewModels.ShopkeeperItemsViewModels; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; 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, IShopkeeperItemService { public ShopkeeperItemService(IMapper mapper, IRepository repository) : base(mapper, repository) { } + + public async Task> 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 }; + } + } } } diff --git a/SessionCompanion/SessionCompanion.Services/Services/ShopkeeperService.cs b/SessionCompanion/SessionCompanion.Services/Services/ShopkeeperService.cs index 3752988..d859da0 100644 --- a/SessionCompanion/SessionCompanion.Services/Services/ShopkeeperService.cs +++ b/SessionCompanion/SessionCompanion.Services/Services/ShopkeeperService.cs @@ -19,6 +19,7 @@ namespace SessionCompanion.Services.Services { public ShopkeeperService(IMapper mapper, IRepository repository) : base(mapper, repository) { } + /// /// Funkcja zmienia status sprzedawcy /// @@ -41,26 +42,27 @@ namespace SessionCompanion.Services.Services await Repository.Update(shopkeeper); } 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(); newActiveShopkeeper.IsAvailable = false; await Repository.Update(newActiveShopkeeper); await Repository.Save(); - return new SuccessResponse("Shopkeepers updated") { StatusCode = 200 }; + return new SuccessResponse(200, "Shopkeepers updated"); } catch (Exception e) { return new ErrorResponse() { StatusCode = 500, Message = e.Message }; } } + public async Task> CreateNewShopKeeper(ShopkeeperWithItemsViewModel shopkeeperWithItemsViewModel) { try { - var activeShopkeeper = await Repository.Get(c => c.IsAvailable.Equals(true)).SingleAsync(); - if (activeShopkeeper is not null && shopkeeperWithItemsViewModel.IsAvailable) + var activeShopkeeper = await Repository.Get(c => c.IsAvailable.Equals(true)).FirstOrDefaultAsync(); + if (activeShopkeeper != null && shopkeeperWithItemsViewModel.IsAvailable) { activeShopkeeper.IsAvailable = false; await Repository.Update(activeShopkeeper); @@ -75,5 +77,23 @@ namespace SessionCompanion.Services.Services return new ErrorResponse() { StatusCode = 500, Message = e.Message }; } } + + public async Task> 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(activeShopkeeper); + return result; + } + catch (Exception e) + { + return new ErrorResponse() { StatusCode = 500, Message = e.Message }; + } + } } } diff --git a/SessionCompanion/SessionCompanion.ViewModels/ShopkeeperItemViewModels/ShopkeeperItemDetailsViewModel.cs b/SessionCompanion/SessionCompanion.ViewModels/ShopkeeperItemViewModels/ShopkeeperItemDetailsViewModel.cs new file mode 100644 index 0000000..574ae8b --- /dev/null +++ b/SessionCompanion/SessionCompanion.ViewModels/ShopkeeperItemViewModels/ShopkeeperItemDetailsViewModel.cs @@ -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 + { + /// + /// Id Sprzedawcy + /// + public int? ShopkeeperId { get; set; } + + /// + /// Id zbroi + /// + public int? ArmorId { get; set; } + + public ArmorViewModel Armor { get; set; } + + /// + /// Id broni + /// + public int? WeaponId { get; set; } + + public WeaponViewModel Weapon { get; set; } + + /// + /// Ilość przedmiotu + /// + public int Amount { get; set; } + } +} diff --git a/SessionCompanion/SessionCompanion.ViewModels/ShopkeeperItemViewModels/ShopkeeperItemViewModel.cs b/SessionCompanion/SessionCompanion.ViewModels/ShopkeeperItemViewModels/ShopkeeperItemViewModel.cs index ca6e236..3298aab 100644 --- a/SessionCompanion/SessionCompanion.ViewModels/ShopkeeperItemViewModels/ShopkeeperItemViewModel.cs +++ b/SessionCompanion/SessionCompanion.ViewModels/ShopkeeperItemViewModels/ShopkeeperItemViewModel.cs @@ -6,20 +6,26 @@ using System.Threading.Tasks; namespace SessionCompanion.ViewModels.ShopkeeperItemsViewModels { + using SessionCompanion.ViewModels.ArmorViewModels; + using SessionCompanion.ViewModels.WeaponViewModels; + public class ShopkeeperItemViewModel { /// /// Id Sprzedawcy /// public int? ShopkeeperId { get; set; } + /// /// Id zbroi /// public int? ArmorId { get; set; } + /// /// Id broni /// public int? WeaponId { get; set; } + /// /// Ilość przedmiotu /// diff --git a/SessionCompanion/SessionCompanion.ViewModels/ShopkeeperViewModels/ShopkeeperWithItemsDetailsViewModel.cs b/SessionCompanion/SessionCompanion.ViewModels/ShopkeeperViewModels/ShopkeeperWithItemsDetailsViewModel.cs new file mode 100644 index 0000000..550f00c --- /dev/null +++ b/SessionCompanion/SessionCompanion.ViewModels/ShopkeeperViewModels/ShopkeeperWithItemsDetailsViewModel.cs @@ -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; } + + /// + /// Nazwa sklepikarza + /// + public string Name { get; set; } + + /// + /// Status sklepikarza + /// + public bool IsAvailable { get; set; } + + /// + /// Lista przedmiotów danego sklepikarza + /// + public IEnumerable Items { get; set; } + } +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/app.module.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/app.module.ts index bd0048f..9cb821f 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/app.module.ts +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/app.module.ts @@ -28,6 +28,7 @@ import { MatDialogModule, MatTooltipModule, MatSnackBarModule, + MatStepperModule, } from '@angular/material'; import { UserService } from '../services/user.service'; 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 { GameMasterShopkeepersTableComponent } from './components/game-master-shopkeepers-table/game-master-shopkeepers-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 { 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'; @@ -64,6 +65,9 @@ import { DragDropModule } from '@angular/cdk/drag-drop'; import { ChooseMonsterDialogComponent } from './components/choose-monster-dialog/choose-monster-dialog.component'; import { CreateCharacterComponent } from './components/create-character/create-character.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({ declarations: [ @@ -92,6 +96,8 @@ import { PersonalizeTemplateComponent } from './components/personalize-template/ MessageDialogComponent, GameMasterTurntrackerComponent, ChooseMonsterDialogComponent, + NewShopkeeperDialogComponent, + PlayerShopComponent, CreateCharacterComponent, PersonalizeTemplateComponent, ], @@ -125,6 +131,7 @@ import { PersonalizeTemplateComponent } from './components/personalize-template/ DynamicModule, MatSnackBarModule, DragDropModule, + MatStepperModule, ], providers: [ UserService, @@ -135,6 +142,7 @@ import { PersonalizeTemplateComponent } from './components/personalize-template/ OtherEquipmentService, MonsterService, EquipmentService, + ShopkeeperService, ], bootstrap: [AppComponent], entryComponents: [ @@ -155,6 +163,8 @@ import { PersonalizeTemplateComponent } from './components/personalize-template/ MessageDialogComponent, GameMasterTurntrackerComponent, ChooseMonsterDialogComponent, + NewShopkeeperDialogComponent, + PlayerShopComponent, CreateCharacterComponent, PersonalizeTemplateComponent, ], diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-character-actions-dialog/actions-components/send-message-action/send-message-action.component.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-character-actions-dialog/actions-components/send-message-action/send-message-action.component.ts index a829ac7..b275ac0 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-character-actions-dialog/actions-components/send-message-action/send-message-action.component.ts +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-character-actions-dialog/actions-components/send-message-action/send-message-action.component.ts @@ -17,7 +17,6 @@ export class SendMessageActionComponent implements OnInit { ngOnInit() {} SendMessage(message: string) { - debugger; this.signalRService.SendMessageToPlayer(this.characterId, message); this.Close(); } diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-character-actions-dialog/game-master-character-actions-dialog.component.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-character-actions-dialog/game-master-character-actions-dialog.component.ts index b3f457b..1362b9b 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-character-actions-dialog/game-master-character-actions-dialog.component.ts +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-character-actions-dialog/game-master-character-actions-dialog.component.ts @@ -31,7 +31,6 @@ export class GameMasterCharacterActionsDialogComponent implements OnInit { this.characterId = this.data.characterid; this.characterName = this.data.characterName; this.inputs = { characterId: this.characterId }; - console.log(this.inputs); } ChangeActionComponent(componentName: string): void { diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-dashboard/game-master-dashboard.component.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-dashboard/game-master-dashboard.component.ts index e95e629..95e26ec 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-dashboard/game-master-dashboard.component.ts +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-dashboard/game-master-dashboard.component.ts @@ -105,6 +105,7 @@ export class GameMasterDashboardComponent implements OnInit, OnDestroy { ngOnInit() { this.signalRService.Login(); + this.UpdateCharactersList(); } UpdateSidenavStatus(sidenav: string, newValue: boolean) { diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/game-master-shopkeepers-table.component.css b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/game-master-shopkeepers-table.component.css index 07509b6..146ed35 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/game-master-shopkeepers-table.component.css +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/game-master-shopkeepers-table.component.css @@ -42,3 +42,9 @@ button { .mat-column-actions { text-align: center; } + +.add-new-shopkeeper-button { + float: right; + color: whitesmoke; + border-color: whitesmoke; +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/game-master-shopkeepers-table.component.html b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/game-master-shopkeepers-table.component.html index 2d8e425..307dece 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/game-master-shopkeepers-table.component.html +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/game-master-shopkeepers-table.component.html @@ -11,23 +11,19 @@ {{row.name}} - - Items Count - {{row.itemsCount}} - - Actions - - @@ -44,3 +40,7 @@ + + diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/game-master-shopkeepers-table.component.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/game-master-shopkeepers-table.component.ts index 25c48b1..17ead8c 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/game-master-shopkeepers-table.component.ts +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/game-master-shopkeepers-table.component.ts @@ -3,6 +3,13 @@ import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; 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({ 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'], }) export class GameMasterShopkeepersTableComponent implements OnInit { - displayedColumns: string[] = ['name', 'itemsCount', 'actions']; - dataSource: MatTableDataSource<{ - name: string; - itemsCount: number; - isAvailable: boolean; - }>; //TODO zmienić na skopkeeper view model + displayedColumns: string[] = ['name', 'actions']; + dataSource: MatTableDataSource; + isAnyAvailable: boolean = false; @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; @ViewChild(MatSort, { static: true }) sort: MatSort; - constructor() {} + constructor( + private shopkeeperService: ShopkeeperService, + public dialog: MatDialog + ) {} ngOnInit() { - this.dataSource = new MatTableDataSource<{ - name: string; - itemsCount: number; - isAvailable: boolean; - }>([ - { - name: 'Test', - itemsCount: 12, - isAvailable: true, - }, - { - name: 'Test2', - itemsCount: 12, - isAvailable: false, - }, - ]); + this.GetAllShopkeepers(); + } + + GetAllShopkeepers() { + this.shopkeeperService + .GetAllShopkeepers() + .pipe(first()) + .subscribe( + (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) => { + console.error(error); + if (error instanceof HttpErrorResponse) { + 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) { diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/shopkeeper-dialogs/new-shopkeeper-dialog/new-shopkeeper-dialog.component.css b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/shopkeeper-dialogs/new-shopkeeper-dialog/new-shopkeeper-dialog.component.css new file mode 100644 index 0000000..6dbe130 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/shopkeeper-dialogs/new-shopkeeper-dialog/new-shopkeeper-dialog.component.css @@ -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; +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/shopkeeper-dialogs/new-shopkeeper-dialog/new-shopkeeper-dialog.component.html b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/shopkeeper-dialogs/new-shopkeeper-dialog/new-shopkeeper-dialog.component.html new file mode 100644 index 0000000..8d3e06f --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/shopkeeper-dialogs/new-shopkeeper-dialog/new-shopkeeper-dialog.component.html @@ -0,0 +1,104 @@ + + +
+ Fill out your shop name + + Name + + +
+
+ +
+
+ + Select items +
+ + + + + + + + + + + + + + + + + + + + + + + +
Select + + + + Name {{row.itemName}} Type {{row.itemType}}
No data found
+ + +
+
+ + +
+
+ + Set a quantity +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name {{row.itemName}} Type {{row.itemType}} Amount + + + + Remove + +
No data found
+ + +
+
+ + +
+
+
+ diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/shopkeeper-dialogs/new-shopkeeper-dialog/new-shopkeeper-dialog.component.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/shopkeeper-dialogs/new-shopkeeper-dialog/new-shopkeeper-dialog.component.ts new file mode 100644 index 0000000..59412fc --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-shopkeepers-table/shopkeeper-dialogs/new-shopkeeper-dialog/new-shopkeeper-dialog.component.ts @@ -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; + selection = new SelectionModel( + true, + [] + ); + finalDataSource: MatTableDataSource; + 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 + ) {} + + ngOnInit() { + this.GetAllWeaponsAndArmors() + .pipe(first()) + .subscribe((res) => { + this.dataSourceWithAllitems = new MatTableDataSource( + res + ); + this.dataSourceWithAllitems.sort = this.sort; + this.dataSourceWithAllitems.paginator = this.paginator; + }); + + this.finalDataSource = new MatTableDataSource( + [] + ); + 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( + (e) => { + return { + shopkeeperId: null, + amount: 1, + armorId: null, + weaponId: e.id, + itemName: e.name, + itemType: 'Weapon', + }; + } + ); + + return [ + ...resultArray, + ...armors.map((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((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 + ); + } + } +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.ts index 00892c3..990f543 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.ts +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/game-master-turntracker/game-master-turntracker.component.ts @@ -116,7 +116,6 @@ export class GameMasterTurntrackerComponent implements OnInit, OnDestroy { ); result.forEach((ele) => { if (monstersOnList.map((e) => e.name).includes(ele.name)) { - debugger; let maxCurrentId = Math.max( ...monstersOnList.map((e) => e.monsterId), 0 diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-dashboard/player-dashboard.component.css b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-dashboard/player-dashboard.component.css index 95aaff4..9e7e493 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-dashboard/player-dashboard.component.css +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-dashboard/player-dashboard.component.css @@ -1,9 +1,16 @@ -@import "../../../styles.css"; +@import '../../../styles.css'; -.toggle-class{ +.toggle-class { margin: 5px; } +.character-name { + width: 240px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .menu-spacer { flex: 1 1 auto; } @@ -36,7 +43,7 @@ background-color: #102028; } -.mat-list-item.active{ +.mat-list-item.active { color: #e9cca7; } diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-dashboard/player-dashboard.component.html b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-dashboard/player-dashboard.component.html index 9920974..797b0dd 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-dashboard/player-dashboard.component.html +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-dashboard/player-dashboard.component.html @@ -5,8 +5,8 @@ menu -
- Session Companion +
+ {{characterName}}
@@ -47,7 +47,7 @@ Profile --> - + shopping_cart Shop diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-dashboard/player-dashboard.component.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-dashboard/player-dashboard.component.ts index 2041330..d74ac9c 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-dashboard/player-dashboard.component.ts +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-dashboard/player-dashboard.component.ts @@ -12,6 +12,8 @@ import { Router } from '@angular/router'; import { ClearCharacterId } from '../../store/actions/player.action'; import { MatSnackBar } from '@angular/material'; import { SnackbarComponent } from '../../shared/snackbar/snackbar.component'; +import { CharacterService } from '../../../services/character.service'; +import { PlayerShopComponent } from '../player-shop/player-shop.component'; @Component({ selector: 'app-player-dashboard', @@ -22,12 +24,14 @@ export class PlayerDashboardComponent implements OnInit { middleComponent; isExpanded = false; selected = false; + characterName: string = ''; constructor( private signalRService: PlayerSignalRService, private store: Store, private router: Router, - private _snackBar: MatSnackBar + private _snackBar: MatSnackBar, + private characterService: CharacterService ) {} ngOnInit() { @@ -38,6 +42,12 @@ export class PlayerDashboardComponent implements OnInit { this.SubscribeToEvents(); this.signalRService.Login(id); 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': this.middleComponent = PlayerOtherEquipmentTableComponent; break; + case 'PlayerShopComponent': + this.middleComponent = PlayerShopComponent; + break; } } @@ -82,6 +95,12 @@ export class PlayerDashboardComponent implements OnInit { gmMessage: result.parameters.message, }, }); + break; + case 'RefreshShopComponent': + this.middleComponent = ''; + + this.middleComponent = PlayerShopComponent; + break; } } ); diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-shop/player-shop.component.css b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-shop/player-shop.component.css new file mode 100644 index 0000000..eb0329a --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-shop/player-shop.component.css @@ -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; +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-shop/player-shop.component.html b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-shop/player-shop.component.html new file mode 100644 index 0000000..5f81186 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-shop/player-shop.component.html @@ -0,0 +1,70 @@ +
+ +
+
+

+ {{shopkeeper.name}} +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name + + {{row.armor.name}} + + + {{row.weapon.name}} + + Type + + Armor + + + Weapon + + Amount {{row.amount}} How many + + + + Actions + +
No data found
+ + +
+
diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-shop/player-shop.component.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-shop/player-shop.component.ts new file mode 100644 index 0000000..f6b76a0 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/components/player-shop/player-shop.component.ts @@ -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; + @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; + @ViewChild(MatSort, { static: true }) sort: MatSort; + + constructor( + private shopkeeperService: ShopkeeperService, + private signalRService: PlayerSignalRService, + private store: Store + ) {} + + 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( + (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; + } + } + ); + } +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/app/shared/signalR-service/player-signalR.service.ts b/SessionCompanion/SessionCompanion/ClientApp/src/app/shared/signalR-service/player-signalR.service.ts index 4216e7f..c085d1e 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/app/shared/signalR-service/player-signalR.service.ts +++ b/SessionCompanion/SessionCompanion/ClientApp/src/app/shared/signalR-service/player-signalR.service.ts @@ -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: {}, + }); + }); } } diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/services/character.service.ts b/SessionCompanion/SessionCompanion/ClientApp/src/services/character.service.ts index f457055..bfab8f7 100644 --- a/SessionCompanion/SessionCompanion/ClientApp/src/services/character.service.ts +++ b/SessionCompanion/SessionCompanion/ClientApp/src/services/character.service.ts @@ -9,6 +9,7 @@ import { LoggedCharactersViewModel } from '../types/viewmodels/character-viewmod import { CharacterStatsViewModel } from '../types/viewmodels/character-viewmodels/CharacterStatsViewModel'; import { CharacterFromTemplatesViewModel } from '../types/viewmodels/character-viewmodels/CharacterFromTemplatesViewModel'; import { SuccessResponse } from '../types/SuccessResponse'; +import { CharacterBasicInfoViewModel } from '../types/viewmodels/character-viewmodels/CharacterBasicInfoViewModel'; Injectable({ providedIn: 'root', @@ -117,4 +118,24 @@ export class CharacterService { }) ); } + + GetCharacterBasicInfo( + characterId: number + ): Observable { + const params = new HttpParams().set('characterId', characterId.toString()); + return this.http + .get>( + this.baseUrl + 'characterBasicInfo', + { params } + ) + .pipe( + switchMap((response) => { + if (response.isLeft) { + return of(response.left); + } else { + return throwError(response.right); + } + }) + ); + } } diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/services/shopkeeper.service.ts b/SessionCompanion/SessionCompanion/ClientApp/src/services/shopkeeper.service.ts new file mode 100644 index 0000000..9cdd2e4 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/services/shopkeeper.service.ts @@ -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 { + return this.http + .get>( + this.baseUrl + 'getShopkeepers' + ) + .pipe( + switchMap((response) => { + if (response.isLeft) { + return of(response.left); + } else { + return throwError(response.right); + } + }) + ); + } + + GetActiveShopkeeper(): Observable { + return this.http + .get>( + this.baseUrl + 'getActiveShopkeeper' + ) + .pipe( + switchMap((response) => { + if (response.isLeft) { + return of(response.left); + } else { + return throwError(response.right); + } + }) + ); + } + + CreateShopkeeper( + shopkeeperModel: ShopkeeperWithItemsViewModel + ): Observable { + const httpOptions = { + headers: new HttpHeaders({ 'Content-Type': 'application/json' }), + }; + + return this.http + .post>( + 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 { + const params = new HttpParams() + .set('shopkeeperId', shopkeeperId.toString()) + .set('availability', newStatus.toString()); + + return this.http + .put>( + 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 { + const params = new HttpParams() + .set('shopkeeperId', shopkeeperId.toString()) + .set('wasAvailable', currentStatus.toString()); + + return this.http + .delete>( + 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 { + 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>( + this.baseUrl + 'buyItem', + null, + { params } + ) + .pipe( + switchMap((response) => { + if (response.isLeft) { + return of(response.left); + } else { + return throwError(response.right); + } + }) + ); + } +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/character-viewmodels/CharacterBasicInfoViewModel.ts b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/character-viewmodels/CharacterBasicInfoViewModel.ts new file mode 100644 index 0000000..c2d6347 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/character-viewmodels/CharacterBasicInfoViewModel.ts @@ -0,0 +1,9 @@ +export interface CharacterBasicInfoViewModel { + id: number; + name: string; + level: number; + currentHealthPoints: number; + maxHealthPoints: number; + class: string; + race: string; +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperItemsViewModel.ts b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperItemsViewModel.ts new file mode 100644 index 0000000..8d68c64 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperItemsViewModel.ts @@ -0,0 +1,6 @@ +export interface ShopkeeperItemsViewModel { + shopkeeperId: number; + armorId: number; + weaponId: number; + amount: number; +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperItemsWithItemNameAndTypeForPlayerViewModel.ts b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperItemsWithItemNameAndTypeForPlayerViewModel.ts new file mode 100644 index 0000000..377004d --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperItemsWithItemNameAndTypeForPlayerViewModel.ts @@ -0,0 +1,9 @@ +export interface ShopkeeperItemsWithItemNameAndTypeForPlayerViewModel { + shopkeeperId: number; + armorId: number; + weaponId: number; + amount: number; + amountToBuy: number; + armor: {}; + weapon: {}; +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperItemsWithItemNameAndTypeViewModel.ts b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperItemsWithItemNameAndTypeViewModel.ts new file mode 100644 index 0000000..d571708 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperItemsWithItemNameAndTypeViewModel.ts @@ -0,0 +1,8 @@ +export interface ShopkeeperItemsWithItemNameAndTypeViewModel { + shopkeeperId: number; + armorId: number; + weaponId: number; + amount: number; + itemName: string; + itemType: string; +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperViewModel.ts b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperViewModel.ts new file mode 100644 index 0000000..cac82ea --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperViewModel.ts @@ -0,0 +1,5 @@ +export interface ShopkeeperViewModel { + id: number; + name: string; + isAvailable: boolean; +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperWithItemsDetailsViewModel.ts b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperWithItemsDetailsViewModel.ts new file mode 100644 index 0000000..377ed2f --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperWithItemsDetailsViewModel.ts @@ -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; + } + ]; +} diff --git a/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperWithItemsViewModel.ts b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperWithItemsViewModel.ts new file mode 100644 index 0000000..0271936 --- /dev/null +++ b/SessionCompanion/SessionCompanion/ClientApp/src/types/viewmodels/shopkeeper-viewmodels/ShopkeeperWithItemsViewModel.ts @@ -0,0 +1,8 @@ +import { ShopkeeperItemsViewModel } from './ShopkeeperItemsViewModel'; + +export interface ShopkeeperWithItemsViewModel { + id: number; + name: string; + isAvailable: boolean; + items: ShopkeeperItemsViewModel[]; +} diff --git a/SessionCompanion/SessionCompanion/Controllers/ShopkeeperController.cs b/SessionCompanion/SessionCompanion/Controllers/ShopkeeperController.cs index 1496a71..674d264 100644 --- a/SessionCompanion/SessionCompanion/Controllers/ShopkeeperController.cs +++ b/SessionCompanion/SessionCompanion/Controllers/ShopkeeperController.cs @@ -11,26 +11,54 @@ using System.Threading.Tasks; namespace SessionCompanion.Controllers { + using Microsoft.AspNetCore.SignalR; + + using SessionCompanion.Hubs; + using SessionCompanion.ViewModels.CharacterArmorViewModels; + using SessionCompanion.ViewModels.CharacterWeaponViewModels; [ApiController] [Route("api/shopkeeper")] public class ShopkeeperController : Controller { private IShopkeeperService _service; - public ShopkeeperController(IShopkeeperService shopkeeperService) + + private IShopkeeperItemService _shopkeeperItemsService; + + private ICharacterArmorService _characterArmorService; + + private ICharacterWeaponService _characterWeaponService; + private IHubContext _sessionHub; + + public ShopkeeperController(IShopkeeperService shopkeeperService, IShopkeeperItemService shopkeeperItemsService, ICharacterWeaponService characterWeaponService, ICharacterArmorService characterArmorService, IHubContext sessionHub) { - _service = shopkeeperService; + this._service = shopkeeperService; + this._shopkeeperItemsService = shopkeeperItemsService; + this._characterArmorService = characterArmorService; + this._characterWeaponService = characterWeaponService; + this._sessionHub = sessionHub; } + /// /// Endpoint zwracający liste sklepikarzy /// /// Lista sklepikarzy [HttpGet("getShopkeepers")] - public async Task> GetShopkeepers() + public async Task, ErrorResponse>> GetShopkeepers() { return _service.Get().ToList(); } + /// + /// Endpoint zwracający aktywnego sklepikarza + /// + /// Lista sklepikarzy + [HttpGet("getActiveShopkeeper")] + public async Task> GetActiveShopkeepers() + { + return await _service.GetActiveShkopkeeperWithItems(); + } + /// /// Endpoint służacy do zmiany statusu sklepikarza /// @@ -38,9 +66,118 @@ namespace SessionCompanion.Controllers /// /// SuccesResponse/ErrorResponse [HttpPut("changeShopkeeperStatus")] - public async Task> ChangeShopkeeperStatus([Required] int shopkeeperId, [Required] bool availability) + public async Task> 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; + } + + /// + /// Metoda kupna przedmiotu od Shopkeepera + /// + /// id sprzedawcy + /// id postaci + /// id broni + /// id pancerza + /// ilosc przedmiotu do kupienia + /// Sukces bądź błąd + [HttpPut("buyItem")] + public async Task> 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> 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> 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"); } } }