Pokemon Entity has changed and reflected in DB. Included additional functionality to edit Pokemon entities to assist with the DB change. Changed how Pokemon Shiny Images/Pokemon Specialty presented in cases where those values are null (Cresselia).

This commit is contained in:
Kira Jiroux 2025-03-21 15:02:07 -04:00
parent d31699f2de
commit 3b0cbecfc1
15 changed files with 464 additions and 18 deletions

View File

@ -13,5 +13,6 @@ namespace Portfolio.Application.Services.PokemonService
Task<Pokemon> GetPokemonByIdAsync(int id);
Task AddPokemonAsync(Pokemon pokemon);
Task DeletePokemonAsync(int pokemonId);
Task UpdatePokemonAsync(Pokemon pokemon);
}
}

View File

@ -37,5 +37,10 @@ namespace Portfolio.Application.Services.PokemonService
{
return await _pokemonRepository.GetPokemonByIdAsync(id);
}
public async Task UpdatePokemonAsync(Pokemon pokemon)
{
await _pokemonRepository.UpdatePokemonAsync(pokemon);
}
}
}

View File

@ -12,6 +12,7 @@ namespace Portfolio.Domain.Features.Pokemon
Task<Pokemon> GetPokemonByIdAsync(int id);
Task AddPokemonAsync(Pokemon pokemon);
Task DeletePokemonAsync(int pokemonId);
Task UpdatePokemonAsync(Pokemon pokemon);
}
}

View File

@ -15,6 +15,8 @@ namespace Portfolio.Domain.Features.Pokemon
public string? VariationName { get; set; }
public required string SleepType { get; set; }
public required string Speciality { get; set; }
public string? PokemonImageUrl { get; set; }
public string? PokemonShinyImageUrl { get; set; }
}

View File

@ -0,0 +1,118 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Portfolio.Infrastructure;
#nullable disable
namespace Portfolio.Infrastructure.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20250321161454_AddPokemonImageUrl")]
partial class AddPokemonImageUrl
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Portfolio.Domain.Features.Pokemon.Pokemon", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<bool>("IsVariation")
.HasColumnType("bit");
b.Property<int>("PokemonId")
.HasColumnType("int");
b.Property<string>("PokemonImageUrl")
.HasColumnType("nvarchar(max)");
b.Property<string>("PokemonName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("SleepType")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Speciality")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("VariationName")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Pokemons");
});
modelBuilder.Entity("Portfolio.Domain.Features.Pokemon_Natures.PokemonNature", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BerryRating")
.HasColumnType("int");
b.Property<int>("IngredientRating")
.HasColumnType("int");
b.Property<string>("Nature")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("SkillRating")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("PokemonNatures");
});
modelBuilder.Entity("Portfolio.Domain.Features.Pokemon_Subskills.PokemonSubskill", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BerryRank")
.HasColumnType("int");
b.Property<int>("IngredientRank")
.HasColumnType("int");
b.Property<int>("SkillRank")
.HasColumnType("int");
b.Property<string>("SubSkill")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("PokemonSubskills");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Portfolio.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddPokemonImageUrl : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "PokemonImageUrl",
table: "Pokemons",
type: "nvarchar(max)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PokemonImageUrl",
table: "Pokemons");
}
}
}

View File

@ -0,0 +1,121 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Portfolio.Infrastructure;
#nullable disable
namespace Portfolio.Infrastructure.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20250321165440_AddPokemonShinyImageUrl")]
partial class AddPokemonShinyImageUrl
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Portfolio.Domain.Features.Pokemon.Pokemon", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<bool>("IsVariation")
.HasColumnType("bit");
b.Property<int>("PokemonId")
.HasColumnType("int");
b.Property<string>("PokemonImageUrl")
.HasColumnType("nvarchar(max)");
b.Property<string>("PokemonName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("PokemonShinyImageUrl")
.HasColumnType("nvarchar(max)");
b.Property<string>("SleepType")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Speciality")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("VariationName")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Pokemons");
});
modelBuilder.Entity("Portfolio.Domain.Features.Pokemon_Natures.PokemonNature", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BerryRating")
.HasColumnType("int");
b.Property<int>("IngredientRating")
.HasColumnType("int");
b.Property<string>("Nature")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("SkillRating")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("PokemonNatures");
});
modelBuilder.Entity("Portfolio.Domain.Features.Pokemon_Subskills.PokemonSubskill", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BerryRank")
.HasColumnType("int");
b.Property<int>("IngredientRank")
.HasColumnType("int");
b.Property<int>("SkillRank")
.HasColumnType("int");
b.Property<string>("SubSkill")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("PokemonSubskills");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Portfolio.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddPokemonShinyImageUrl : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "PokemonShinyImageUrl",
table: "Pokemons",
type: "nvarchar(max)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PokemonShinyImageUrl",
table: "Pokemons");
}
}
}

View File

@ -35,10 +35,16 @@ namespace Portfolio.Infrastructure.Migrations
b.Property<int>("PokemonId")
.HasColumnType("int");
b.Property<string>("PokemonImageUrl")
.HasColumnType("nvarchar(max)");
b.Property<string>("PokemonName")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("PokemonShinyImageUrl")
.HasColumnType("nvarchar(max)");
b.Property<string>("SleepType")
.IsRequired()
.HasColumnType("nvarchar(max)");
@ -55,7 +61,7 @@ namespace Portfolio.Infrastructure.Migrations
b.ToTable("Pokemons");
});
modelBuilder.Entity("Portfolio.Domain.Features.Pokemon_Natures.PokemonNatures", b =>
modelBuilder.Entity("Portfolio.Domain.Features.Pokemon_Natures.PokemonNature", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
@ -81,7 +87,7 @@ namespace Portfolio.Infrastructure.Migrations
b.ToTable("PokemonNatures");
});
modelBuilder.Entity("Portfolio.Domain.Features.Pokemon_Subskills.PokemonSubskills", b =>
modelBuilder.Entity("Portfolio.Domain.Features.Pokemon_Subskills.PokemonSubskill", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()

View File

@ -41,5 +41,10 @@ namespace Portfolio.Infrastructure.Repositories
}
}
public async Task UpdatePokemonAsync(Pokemon pokemon)
{
_context.Pokemons.Update(pokemon);
await _context.SaveChangesAsync();
}
}
}

View File

@ -1,5 +1,6 @@
@inject IPokemonService PokemonService
@inject IJSRuntime JS
@inject NavigationManager Navigation
@attribute [StreamRendering]
@rendermode InteractiveServer
@ -18,8 +19,9 @@ else
<!-- Table Header -->
<div class="card-header bg-secondary bg-gradient ml-0 py-3">
<div class="row">
<div class="col-12 text-center">
<div class="col-12 text-center position-rel">
<h2 class="text-info">Available Pokémon</h2>
<div class="m-1 col-1 badge bg-info position-absolute top-0 end-0"><p class="statText">@(pokemons.Count()) Pokemon</p></div>
</div>
</div>
</div>
@ -46,23 +48,30 @@ else
{
<tr>
<!-- Section: Pokemon Image -->
<!-- Section: Pokemon Image -->
@{
string baseUrl = pokemon.IsVariation
? $"/pokemon_images/normal/{pokemon.PokemonId}-{pokemon.VariationName.ToLower()}{pokemon.PokemonName.ToLower()}.png"
: $"/pokemon_images/normal/{pokemon.PokemonId}.png";
string shinyUrl = pokemon.IsVariation
? $"/pokemon_images/shiny/{pokemon.PokemonId}-{pokemon.VariationName.ToLower()}{pokemon.PokemonName.ToLower()}.png"
: $"/pokemon_images/shiny/{pokemon.PokemonId}.png";
string baseUrl = pokemon.PokemonImageUrl;
string shinyUrl = pokemon.PokemonShinyImageUrl;
}
<td style="text-align: center;">
<div class="flip-container" @onclick="() => ToggleImage(pokemon.Id)">
<div class="flipper @(isShiny[pokemon.Id] ? "flipped" : "")">
<img class="front" src="@baseUrl" />
<img class="back" src="@shinyUrl" />
@if(shinyUrl == null){
<div class="flip-container">
<div class="flipper">
<img class="front" src="@baseUrl" />
</div>
</div>
</div>
}
else
{
<div class="flip-container" @onclick="() => ToggleImage(pokemon.Id)">
<div class="flipper @(isShiny[pokemon.Id] ? "flipped" : "")">
<img class="front" src="@baseUrl" />
<img class="back" src="@shinyUrl" />
</div>
</div>
}
</td>
<!-- Section 2: Pokemon # -->
@ -95,8 +104,13 @@ else
<div class="m-1 col-1 badge @pokemon.Speciality.ToLower()"><p class="statText">@pokemon.Speciality</p></div>
</td>
<!-- Section 6: Delete Button -->
<!-- Section 6: Functional Buttons -->
<td>
<button class="btn btn-warning" @onclick="() => EditPokemon(pokemon.Id)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-fill" viewBox="0 0 16 16">
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.5.5 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11z" />
</svg>
</button>
<button class="btn btn-danger" @onclick="() => ConfirmDelete(pokemon.Id)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-trash" viewBox="0 0 16 16">

View File

@ -1,4 +1,5 @@
using Microsoft.JSInterop;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.JSInterop;
using Portfolio.Domain.Features.Pokemon;
namespace Portfolio.WebUI.Server.Components.Component
@ -48,5 +49,10 @@ namespace Portfolio.WebUI.Server.Components.Component
pokemons.RemoveAll(p => p.Id == Id); // Remove from the list locally
StateHasChanged(); // Refresh the UI
}
private void EditPokemon(int id)
{
Navigation.NavigateTo($"/pokemonsleep/edit/{id}");
}
}
}

View File

@ -30,6 +30,14 @@
transform: rotateY(180deg);
}
.pokeimage {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.front, .back {
width: 100%;
height: 100%;

View File

@ -80,8 +80,19 @@ else
<option value="Berries">Berries</option>
<option value="Ingredients">Ingredients</option>
<option value="Skills">Skills</option>
<option value="Other">Other</option>
</InputSelect>
</div>
<!-- New Image URL Field -->
<div class="row mb-3 m-auto">
<label for="ImageUrl" class="form-label">Base Image URL</label>
<InputText id="ImageUrl" @bind-Value="NewPokemon.PokemonImageUrl" class="form-control" />
</div>
<div class="row mb-3 m-auto">
<label for="ImageUrl" class="form-label">Shiny Image URL</label>
<InputText id="ImageUrl" @bind-Value="NewPokemon.PokemonShinyImageUrl" class="form-control" />
</div>
<button type="submit" class="btn btn-primary mb-3">Add Pokémon</button>
<button type="button" class="btn btn-secondary mb-3" @onclick="@Cancel">Cancel</button>
</EditForm>

View File

@ -0,0 +1,92 @@
@page "/pokemonsleep/edit/{id:int}"
@inject IPokemonService PokemonService
@inject NavigationManager Navigation
@attribute [StreamRendering]
@rendermode InteractiveServer
<PageTitle>Edit Pokémon</PageTitle>
<PokemonHeader />
@if (pokemon == null)
{
<p><em>Loading...</em></p>
}
else
{
<div class="w-50 mt-5 m-auto create-container">
<div class="card-header bg-secondary bg-gradient ml-0 py-3">
<div class="row">
<div class="col-12 text-center">
<h2 class="text-info">Edit Pokémon</h2>
</div>
</div>
</div>
<div class="container m-lg-1">
<EditForm class="col mb-3" Model="pokemon" OnValidSubmit="HandleSubmit">
<DataAnnotationsValidator />
<div class="row">
<div class="col-sm-3 input-group mb-3">
<span for="PokemonId" class="input-group-text">#</span>
<InputNumber min="0" id="PokemonId" @bind-Value="pokemon.PokemonId" class="form-control" required disabled />
<InputText id="PokemonName" @bind-Value="pokemon.PokemonName" class="form-control w-75" required disabled />
</div>
</div>
<div class="row mb-3 m-auto">
<label for="SleepType" class="form-label">Sleep Type</label>
<InputSelect id="SleepType" @bind-Value="pokemon.SleepType" class="form-select">
<option value="Dozing">Dozing</option>
<option value="Snoozing">Snoozing</option>
<option value="Slumbering">Slumbering</option>
</InputSelect>
</div>
<div class="row mb-3 m-auto">
<label for="Speciality" class="form-label">Specialty</label>
<InputSelect id="Speciality" @bind-Value="pokemon.Speciality" class="form-select">
<option value="Berries">Berries</option>
<option value="Ingredients">Ingredients</option>
<option value="Skills">Skills</option>
</InputSelect>
</div>
<!-- New Image URL Field -->
<div class="row mb-3 m-auto">
<label for="ImageUrl" class="form-label">Base Image URL</label>
<InputText id="ImageUrl" @bind-Value="pokemon.PokemonImageUrl" class="form-control" />
</div>
<div class="row mb-3 m-auto">
<label for="ImageUrl" class="form-label">Shiny Image URL</label>
<InputText id="ImageUrl" @bind-Value="pokemon.PokemonShinyImageUrl" class="form-control" />
</div>
<button type="submit" class="btn btn-primary mb-3">Save Changes</button>
<button type="button" class="btn btn-secondary mb-3" @onclick="Cancel">Cancel</button>
</EditForm>
</div>
</div>
}
@code {
[Parameter] public int Id { get; set; }
private Pokemon? pokemon;
protected override async Task OnInitializedAsync()
{
pokemon = await PokemonService.GetPokemonByIdAsync(Id);
}
private async Task HandleSubmit()
{
await PokemonService.UpdatePokemonAsync(pokemon);
Navigation.NavigateTo("/pokemonsleep");
}
private void Cancel()
{
Navigation.NavigateTo("/pokemonsleep");
}
}