Compare commits

..

3 Commits

17 changed files with 578 additions and 425 deletions

View File

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Components;
namespace Portfolio.WebUI.Server.Components.Component
{
public partial class Loading
{
[Parameter]
public string LoadingString { get; set; }
}
}

View File

@ -1,152 +0,0 @@
@inject IPokemonService PokemonService
<div class="create-container m-auto bg-info border border-5 border-info-subtle rounded-4 p-3">
<EditForm class="col" Model="NewPokemon">
<DataAnnotationsValidator />
<div class="bg-primary-subtle rounded"><p class="fs-3 fw-light text-center card-title">New Pokemon</p></div>
<!-- Pokemon Number and Name -->
<div class="row mt-1">
<div class="col input-group mb-2">
<span class="input-group-text text-sm-center rounded-start">#</span>
<InputNumber min="1"
placeholder="Pokedex #"
id="PokemonId"
@bind-Value="NewPokemon.PokemonId"
class="form-control "
type="number" />
<InputText placeholder="Pokemon Name"
id="PokemonName"
@bind-Value="NewPokemon.PokemonName"
class="form-control w-50 rounded-end" />
</div>
</div>
<!-- Variation Check -->
<div class="d-flex flex-row justify-content-start input-group ">
<InputCheckbox id="IsVariation"
@bind-Value="NewPokemon.IsVariation"
@onclick="@Toggle"
class="form-check-input p-3 rounded" />
<span class="input-group-text ms-1 @GetRoundingClass()">Variation?</span>
<InputText placeholder="How So?"
id="VariationName"
@bind-Value="NewPokemon.VariationName"
class="form-control rounded-end"
hidden="@HideLabel" />
</div>
<!-- <br> -->
<div class="border-bottom border-3 border-info-subtle rounded m-1 my-3"></div>
<!-- Pokemon Type -->
<div class="row mb-2">
<div class="input-group m-auto">
<span for="PokemonType" class="input-group-text rounded-start">Pokemon Type</span>
<InputSelect id="PokemonType" @bind-Value="NewPokemon.PokemonType" class="form-select rounded-end">
<option disabled value="" selected>Select...</option>
@foreach (var pt in PokemonTypes)
{
<option value="@pt">@pt</option>
}
</InputSelect>
</div>
</div>
<!-- Pokemon Sleep Type, Specialty -->
<div class="row mb-3 mx-0">
<!-- Sleep Type -->
<div class="col ps-0 pe-1">
<div class="row input-group m-auto">
<span for="SleepType" class="input-group-text rounded-top">Sleep Type</span>
</div>
<div class="row input-group m-auto">
<InputSelect id="SleepType" @bind-Value="NewPokemon.SleepType" class="form-select rounded-bottom">
<option disabled value="" selected>Select...</option>
@foreach (var st in SleepTypes)
{
<option value="@st">@st</option>
}
</InputSelect>
</div>
</div>
<!-- Speciality -->
<div class="col ps-1 pe-0">
<div class="row input-group m-auto">
<span for="Speciality" class="input-group-text rounded-top">Specialty</span>
</div>
<div class="row input-group m-auto">
<InputSelect id="Speciality" @bind-Value="NewPokemon.Speciality" class="form-select rounded-bottom">
<option disabled value="" selected>Select...</option>
@foreach (var sp in Specialities)
{
<option value="@sp">@sp</option>
}
</InputSelect>
</div>
</div>
</div>
<!-- <br> -->
<div class="border-bottom border-3 border-info-subtle rounded m-1 my-3"></div>
<!-- Images -->
<div class="row mb-2">
<div class="input-group m-auto">
<span for="ImageUrl" class="input-group-text rounded-start">Base Image URL</span>
<InputText id="ImageUrl" @bind-Value="NewPokemon.PokemonImageUrl" class="form-control rounded-end" />
</div>
</div>
<div class="row mb-2">
<div class="input-group m-auto">
<span for="ShinyImageUrl" class="input-group-text rounded-start">Shiny Image URL</span>
<InputText id="ShinyImageUrl" @bind-Value="NewPokemon.PokemonShinyImageUrl" class="form-control rounded-end" />
</div>
</div>
<!-- Flavor -->
<div class="row mb-2">
<div class="input-group m-auto">
<span for="FlavorText" class="input-group-text rounded-start">Flavor Text</span>
<InputText id="FlavorText" @bind-Value="NewPokemon.FlavorText" class="form-control rounded-end" />
</div>
</div>
<!-- <br> -->
<div class="border-bottom border-3 border-info-subtle rounded m-1 my-3"></div>
@if (showErrors && !IsComplete)
{
<div class="alert alert-warning mt-2">
Please complete: @string.Join(", ", MissingFields())
</div>
}
<div class="d-flex mt-3 justify-content-center gap-3">
<button type="button"
class="btn btn-primary rounded p-1 px-0"
@onclick="@SendPokemon"
disabled="@( !IsComplete )">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-file-arrow-up" viewBox="0 0 16 16">
<path d="M8 11a.5.5 0 0 0 .5-.5V6.707l1.146 1.147a.5.5 0 0 0 .708-.708l-2-2a.5.5 0 0 0-.708 0l-2 2a.5.5 0 1 0 .708.708L7.5 6.707V10.5a.5.5 0 0 0 .5.5" />
<path d="M4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm0 1h8a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1" />
</svg>
</button>
@if(mostRecentForm)
{
<button type="button"
class="btn btn-danger rounded p-1 px-0"
@onclick="@HandleRemove">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-file-minus" viewBox="0 0 16 16">
<path d="M5.5 8a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1H6a.5.5 0 0 1-.5-.5" />
<path d="M4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm0 1h8a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1" />
</svg>
</button>
}
</div>
</EditForm>
</div>

View File

@ -1,101 +0,0 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Portfolio.Application.Services.PokemonService;
using Portfolio.Domain.Features.Pokemon;
namespace Portfolio.WebUI.Server.Components.Component.Pokemon_Components
{
public partial class PokemonAddForm
{
[Parameter]
public EventCallback<Pokemon> OnPokemonReady { get; set; }
[Parameter]
public EventCallback RemoveForm { get; set; }
[Parameter]
public bool mostRecentForm { get; set; }
protected static readonly string[] PokemonTypes = new[]
{
"Grass","Fire","Water","Normal","Flying","Bug","Poison","Electric","Ground","Rock","Ice",
"Steel","Fighting","Psychic","Dark","Fairy","Ghost","Dragon"
};
protected static readonly string[] SleepTypes = new[] { "Dozing", "Snoozing", "Slumbering" };
protected static readonly string[] Specialities = new[] { "Berries", "Ingredients", "Skills", "All" };
private bool HideLabel { get; set; } = true;
private bool showErrors { get; set; } = false;
private Pokemon NewPokemon = new Pokemon
{
PokemonId = 0, // Or any default ID logic
PokemonName = string.Empty, // Required fields cannot be null
SleepType = string.Empty,
Speciality = string.Empty,
IsVariation = false
};
private void Toggle() => HideLabel = !HideLabel;
private string GetRoundingClass()
{
if (!HideLabel) {
return "rounded-start";
}
return "rounded-start rounded-end";
}
// Minimal "complete" check (no data annotations needed)
private bool IsComplete =>
NewPokemon.PokemonId > 0 &&
!string.IsNullOrWhiteSpace(NewPokemon.PokemonName) &&
!string.IsNullOrWhiteSpace(NewPokemon.PokemonType) &&
!string.IsNullOrWhiteSpace(NewPokemon.SleepType) &&
!string.IsNullOrWhiteSpace(NewPokemon.Speciality) &&
(!NewPokemon.IsVariation || !string.IsNullOrWhiteSpace(NewPokemon.VariationName));
private IEnumerable<string> MissingFields()
{
if (NewPokemon.PokemonId <= 0) yield return "Pokédex #";
if (string.IsNullOrWhiteSpace(NewPokemon.PokemonName)) yield return "Name";
if (string.IsNullOrWhiteSpace(NewPokemon.PokemonType)) yield return "Type";
if (string.IsNullOrWhiteSpace(NewPokemon.SleepType)) yield return "Sleep Type";
if (string.IsNullOrWhiteSpace(NewPokemon.Speciality)) yield return "Specialty";
if (NewPokemon.IsVariation && string.IsNullOrWhiteSpace(NewPokemon.VariationName)) yield return "Variation Name";
}
private async Task HandleRemove()
{
await RemoveForm.InvokeAsync();
}
private async Task SendPokemon()
{
if (!IsComplete)
{
showErrors = true;
StateHasChanged();
return;
}
// Optionally send a copy to avoid later mutation by the child
var copy = new Pokemon
{
PokemonId = NewPokemon.PokemonId,
PokemonName = NewPokemon.PokemonName,
PokemonType = NewPokemon.PokemonType,
SleepType = NewPokemon.SleepType,
Speciality = NewPokemon.Speciality,
IsVariation = NewPokemon.IsVariation,
VariationName = NewPokemon.VariationName,
PokemonImageUrl = NewPokemon.PokemonImageUrl,
PokemonShinyImageUrl = NewPokemon.PokemonShinyImageUrl,
FlavorText = NewPokemon.FlavorText
};
await OnPokemonReady.InvokeAsync(copy);
}
}
}

View File

@ -5,7 +5,7 @@ using System.Text.Json;
namespace Portfolio.WebUI.Server.Components.Component.Pokemon_Components namespace Portfolio.WebUI.Server.Components.Component.Pokemon_Components
{ {
partial class PokemonDownload partial class PokemonDownloadButton
{ {
[Parameter] [Parameter]
public List<Pokemon> _Pokemon { get; set; } public List<Pokemon> _Pokemon { get; set; }

View File

@ -0,0 +1,308 @@
@inject IPokemonService PokemonService
@if(formUse == "ADD")
{
<div class="pokemon-form-container m-auto bg-info border border-5 border-info-subtle rounded-4 p-3">
<EditForm class="col" Model="NewPokemon">
<DataAnnotationsValidator />
<div class="bg-primary-subtle rounded"><p class="fs-3 fw-light text-center card-title">New Pokemon</p></div>
<!-- Pokemon Number and Name -->
<div class="row mt-1">
<div class="col input-group mb-2">
<span class="input-group-text text-sm-center rounded-start">#</span>
<InputNumber min="1"
placeholder="Pokedex #"
id="PokemonId"
@bind-Value="NewPokemon.PokemonId"
@onchange="@SendPokemon"
class="form-control "
type="number" />
<InputText placeholder="Pokemon Name"
id="PokemonName"
@bind-Value="NewPokemon.PokemonName"
@onchange="@SendPokemon"
class="form-control w-50 rounded-end" />
</div>
</div>
<!-- Variation Check -->
<div class="d-flex flex-row justify-content-start input-group ">
<InputCheckbox id="IsVariation"
@bind-Value="NewPokemon.IsVariation"
@onclick="@Toggle"
@onchange="@SendPokemon"
class="form-check-input p-3 rounded" />
<span class="input-group-text ms-1 @GetRoundingClass()">Variation?</span>
<InputText placeholder="How So?"
id="VariationName"
@bind-Value="NewPokemon.VariationName"
@onchange="@SendPokemon"
class="form-control rounded-end"
hidden="@HideLabel" />
</div>
<!-- <br> -->
<div class="border-bottom border-3 border-info-subtle rounded m-1 my-3"></div>
<!-- Pokemon Type -->
<div class="row mb-2">
<div class="input-group m-auto">
<span for="PokemonType" class="input-group-text rounded-start">Pokemon Type</span>
<InputSelect id="PokemonType" @bind-Value="NewPokemon.PokemonType" @onchange="@SendPokemon" class="form-select rounded-end">
<option disabled value="" selected>Select...</option>
@foreach (var pt in PokemonTypes)
{
<option value="@pt">@pt</option>
}
</InputSelect>
</div>
</div>
<!-- Pokemon Sleep Type, Specialty -->
<div class="row mb-3 mx-0">
<!-- Sleep Type -->
<div class="col ps-0 pe-1">
<div class="row input-group m-auto">
<span for="SleepType" class="input-group-text rounded-top">Sleep Type</span>
</div>
<div class="row input-group m-auto">
<InputSelect id="SleepType" @bind-Value="NewPokemon.SleepType" @onchange="@SendPokemon" class="form-select rounded-bottom">
<option disabled value="" selected>Select...</option>
@foreach (var st in SleepTypes)
{
<option value="@st">@st</option>
}
</InputSelect>
</div>
</div>
<!-- Speciality -->
<div class="col ps-1 pe-0">
<div class="row input-group m-auto">
<span for="Speciality" class="input-group-text rounded-top">Specialty</span>
</div>
<div class="row input-group m-auto">
<InputSelect id="Speciality" @bind-Value="NewPokemon.Speciality" @onchange="@SendPokemon" class="form-select rounded-bottom">
<option disabled value="" selected>Select...</option>
@foreach (var sp in Specialities)
{
<option value="@sp">@sp</option>
}
</InputSelect>
</div>
</div>
</div>
<!-- <br> -->
<div class="border-bottom border-3 border-info-subtle rounded m-1 my-3"></div>
<!-- Images -->
<div class="row mb-2">
<div class="input-group m-auto">
<span for="ImageUrl" class="input-group-text rounded-start">Base Image URL</span>
<InputText id="ImageUrl" @bind-Value="NewPokemon.PokemonImageUrl" @onchange="@SendPokemon" class="form-control rounded-end" />
</div>
</div>
<div class="row mb-2">
<div class="input-group m-auto">
<span for="ShinyImageUrl" class="input-group-text rounded-start">Shiny Image URL</span>
<InputText id="ShinyImageUrl" @bind-Value="NewPokemon.PokemonShinyImageUrl" @onchange="@SendPokemon" class="form-control rounded-end" />
</div>
</div>
<!-- Flavor -->
<div class="row mb-2">
<div class="input-group m-auto">
<span for="FlavorText" class="input-group-text rounded-start">Flavor Text</span>
<InputText id="FlavorText" @bind-Value="NewPokemon.FlavorText" @onchange="@SendPokemon" class="form-control rounded-end" />
</div>
</div>
<!-- <br> -->
<div class="border-bottom border-3 border-info-subtle rounded m-1 my-3"></div>
@if (showErrors && !IsComplete)
{
<div class="alert alert-warning mt-2">
Please complete: @string.Join(", ", MissingFields())
</div>
}
<div class="d-flex mt-3 justify-content-center gap-3">
@if (mostRecentForm)
{
<button type="button"
class="btn btn-danger rounded rounded-5 p-1"
@onclick="@HandleRemove">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-x-circle" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16" />
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708" />
</svg>
</button>
}
else
{
<button type="button"
class="btn btn-danger rounded rounded-5 p-1"
disabled>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-x-circle" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16" />
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708" />
</svg>
</button>
}
</div>
</EditForm>
</div>
}
else if (formUse == "EDIT")
{
<div class="pokemon-form-container m-auto bg-info border border-5 border-info-subtle rounded-4 p-3">
<EditForm class="col" Model="PokemonToEdit">
<DataAnnotationsValidator />
<div class="bg-primary-subtle rounded"><p class="fs-3 fw-light text-center card-title">Edit Pokemon</p></div>
<!-- Pokemon Number and Name -->
<div class="row mt-1">
<div class="col input-group mb-2">
<span class="input-group-text text-sm-center rounded-start">#</span>
<InputNumber min="1"
placeholder="Pokedex #"
id="PokemonId"
@bind-Value="PokemonToEdit.PokemonId"
@onchange="@SendPokemon"
class="form-control "
type="number" />
<InputText placeholder="Pokemon Name"
id="PokemonName"
@bind-Value="PokemonToEdit.PokemonName"
@onchange="@SendPokemon"
class="form-control w-50 rounded-end" />
</div>
</div>
<!-- Variation Check -->
<div class="d-flex flex-row justify-content-start input-group ">
<InputCheckbox id="IsVariation"
@bind-Value="PokemonToEdit.IsVariation"
@onclick="@Toggle"
@onchange="@SendPokemon"
class="form-check-input p-3 rounded" />
<span class="input-group-text ms-1 @GetRoundingClass()">Variation?</span>
<InputText placeholder="How So?"
id="VariationName"
@bind-Value="PokemonToEdit.VariationName"
@onchange="@SendPokemon"
class="form-control rounded-end"
hidden="@HideLabel" />
</div>
<!-- <br> -->
<div class="border-bottom border-3 border-info-subtle rounded m-1 my-3"></div>
<!-- Pokemon Type -->
<div class="row mb-2">
<div class="input-group m-auto">
<span for="PokemonType" class="input-group-text rounded-start">Pokemon Type</span>
<InputSelect id="PokemonType" @bind-Value="PokemonToEdit.PokemonType" class="form-select rounded-end" @onchange="@SendPokemon">
<option disabled value="" selected>Select...</option>
@foreach (var pt in PokemonTypes)
{
<option value="@pt">@pt</option>
}
</InputSelect>
</div>
</div>
<!-- Pokemon Sleep Type, Specialty -->
<div class="row mb-3 mx-0">
<!-- Sleep Type -->
<div class="col ps-0 pe-1">
<div class="row input-group m-auto">
<span for="SleepType" class="input-group-text rounded-top">Sleep Type</span>
</div>
<div class="row input-group m-auto">
<InputSelect id="SleepType" @bind-Value="PokemonToEdit.SleepType" class="form-select rounded-bottom" @onchange="@SendPokemon">
<option disabled value="" selected>Select...</option>
@foreach (var st in SleepTypes)
{
<option value="@st">@st</option>
}
</InputSelect>
</div>
</div>
<!-- Speciality -->
<div class="col ps-1 pe-0">
<div class="row input-group m-auto">
<span for="Speciality" class="input-group-text rounded-top">Specialty</span>
</div>
<div class="row input-group m-auto">
<InputSelect id="Speciality" @bind-Value="PokemonToEdit.Speciality" class="form-select rounded-bottom" @onchange="@SendPokemon">
<option disabled value="" selected>Select...</option>
@foreach (var sp in Specialities)
{
<option value="@sp">@sp</option>
}
</InputSelect>
</div>
</div>
</div>
<!-- <br> -->
<div class="border-bottom border-3 border-info-subtle rounded m-1 my-3"></div>
<!-- Images -->
<div class="row mb-2">
<div class="input-group m-auto">
<span for="ImageUrl" class="input-group-text rounded-start">Base Image URL</span>
<InputText id="ImageUrl" @bind-Value="PokemonToEdit.PokemonImageUrl" class="form-control rounded-end" @onchange="@SendPokemon" />
</div>
</div>
<div class="row mb-2">
<div class="input-group m-auto">
<span for="ShinyImageUrl" class="input-group-text rounded-start">Shiny Image URL</span>
<InputText id="ShinyImageUrl" @bind-Value="PokemonToEdit.PokemonShinyImageUrl" class="form-control rounded-end" @onchange="@SendPokemon" />
</div>
</div>
<!-- Flavor -->
<div class="row mb-2">
<div class="input-group m-auto">
<span for="FlavorText" class="input-group-text rounded-start">Flavor Text</span>
<InputText id="FlavorText" @bind-Value="PokemonToEdit.FlavorText" class="form-control rounded-end" @onchange="@SendPokemon" />
</div>
</div>
<!-- <br> -->
<div class="border-bottom border-3 border-info-subtle rounded m-1 my-3"></div>
@if (showErrors && !IsComplete)
{
<div class="alert alert-warning mt-2">
Please complete: @string.Join(", ", MissingFields())
</div>
}
<div class="d-flex mt-3 justify-content-center gap-3">
@if (mostRecentForm)
{
<button type="button"
class="btn btn-danger rounded rounded-5 p-1"
@onclick="@HandleRemove">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-x-circle" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16" />
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708" />
</svg>
</button>
}
</div>
</EditForm>
</div>
}

View File

@ -0,0 +1,162 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Portfolio.Application.Services.PokemonService;
using Portfolio.Domain.Features.Pokemon;
namespace Portfolio.WebUI.Server.Components.Component.Pokemon_Components
{
public partial class PokemonForm
{
// To Add or Edit Pokemon
[Parameter]
public string formUse { get; set; }
[Parameter]
public EventCallback<Pokemon> OnPokemonReady { get; set; }
[Parameter]
public EventCallback RemoveForm { get; set; }
// When Adding
[Parameter]
public bool mostRecentForm { get; set; }
private Pokemon NewPokemon = new Pokemon
{
PokemonId = 0, // Or any default ID logic
PokemonName = string.Empty, // Required fields cannot be null
SleepType = string.Empty,
Speciality = string.Empty,
IsVariation = false
};
// When Editing
[Parameter]
public Pokemon? PokemonToEdit { get; set; }
private int PokemonToEditId { get; set; }
// General Form
protected static readonly string[] PokemonTypes = new[]
{
"Grass","Fire","Water","Normal","Flying","Bug","Poison","Electric","Ground","Rock","Ice",
"Steel","Fighting","Psychic","Dark","Fairy","Ghost","Dragon"
};
protected static readonly string[] SleepTypes = new[] { "Dozing", "Snoozing", "Slumbering" };
protected static readonly string[] Specialities = new[] { "Berries", "Ingredients", "Skills", "All" };
private bool HideLabel { get; set; }
private bool showErrors { get; set; } = false;
protected override async Task OnInitializedAsync()
{
if (formUse == "EDIT")
{
if (PokemonToEdit.IsVariation == true)
{
HideLabel = false;
}
PokemonToEditId = PokemonToEdit.Id;
}
else
{
HideLabel = true;
}
}
private void Toggle()
{
HideLabel = !HideLabel;
}
// CSS-function to get proper styling for form elements
private string GetRoundingClass()
{
if (!HideLabel)
{
return "rounded-start";
}
return "rounded-start rounded-end";
}
// Minimal "complete" check (no data annotations needed)
private bool IsComplete =>
NewPokemon.PokemonId > 0 &&
!string.IsNullOrWhiteSpace(NewPokemon.PokemonName) &&
!string.IsNullOrWhiteSpace(NewPokemon.PokemonType) &&
!string.IsNullOrWhiteSpace(NewPokemon.SleepType) &&
!string.IsNullOrWhiteSpace(NewPokemon.Speciality) &&
(!NewPokemon.IsVariation || !string.IsNullOrWhiteSpace(NewPokemon.VariationName));
private IEnumerable<string> MissingFields()
{
if (NewPokemon.PokemonId <= 0) yield return "Pokédex #";
if (string.IsNullOrWhiteSpace(NewPokemon.PokemonName)) yield return "Name";
if (string.IsNullOrWhiteSpace(NewPokemon.PokemonType)) yield return "Type";
if (string.IsNullOrWhiteSpace(NewPokemon.SleepType)) yield return "Sleep Type";
if (string.IsNullOrWhiteSpace(NewPokemon.Speciality)) yield return "Specialty";
if (NewPokemon.IsVariation && string.IsNullOrWhiteSpace(NewPokemon.VariationName)) yield return "Variation Name";
}
private async Task HandleRemove()
{
await RemoveForm.InvokeAsync();
}
private async Task SendPokemon()
{
if(formUse == "ADD")
{
if (!IsComplete)
{
showErrors = true;
StateHasChanged();
return;
}
// Optionally send a copy to avoid later mutation by the child
var copy = new Pokemon
{
PokemonId = NewPokemon.PokemonId,
PokemonName = NewPokemon.PokemonName,
PokemonType = NewPokemon.PokemonType,
SleepType = NewPokemon.SleepType,
Speciality = NewPokemon.Speciality,
IsVariation = NewPokemon.IsVariation,
VariationName = NewPokemon.VariationName,
PokemonImageUrl = NewPokemon.PokemonImageUrl,
PokemonShinyImageUrl = NewPokemon.PokemonShinyImageUrl,
FlavorText = NewPokemon.FlavorText
};
await OnPokemonReady.InvokeAsync(copy);
}
else
{
// Optionally send a copy to avoid later mutation by the child
var edit = new Pokemon
{
Id = PokemonToEditId,
PokemonId = PokemonToEdit.PokemonId,
PokemonName = PokemonToEdit.PokemonName,
PokemonType = PokemonToEdit.PokemonType,
SleepType = PokemonToEdit.SleepType,
Speciality = PokemonToEdit.Speciality,
IsVariation = PokemonToEdit.IsVariation,
VariationName = PokemonToEdit.VariationName,
PokemonImageUrl = PokemonToEdit.PokemonImageUrl,
PokemonShinyImageUrl = PokemonToEdit.PokemonShinyImageUrl,
FlavorText = PokemonToEdit.FlavorText
};
await OnPokemonReady.InvokeAsync(edit);
Console.WriteLine(edit);
}
}
}
}

View File

@ -8,10 +8,8 @@ $display-font-sizes: (
6: 2.5rem 6: 2.5rem
); );
.create-container { .pokemon-form-container {
position: relative; position: relative;
width: 100%;
max-width: 390px; /* Prevent it from getting too huge */
aspect-ratio: 3 / 4; /* Maintains card shape dynamically */ aspect-ratio: 3 / 4; /* Maintains card shape dynamically */
background-color: var(--bg-color); background-color: var(--bg-color);
border-width: .5rem; border-width: .5rem;
@ -24,5 +22,4 @@ $display-font-sizes: (
.checkbox-styling { .checkbox-styling {
width: 100px; width: 100px;
height: 100px; height: 100px;
} }

View File

@ -1,16 +1,16 @@
 
<!-- Search Input --> <!-- Search Input -->
<div class="pokemon-selector p-3 bg-light"> <div class="pokemon-selector p-3 bg-light">
<input class="form-control mb-3" placeholder="Search Pokémon..." @bind="SearchTerm" @oninput="HandleSearch" /> <input class="form-control mb-3 rounded rounded-5" placeholder="Search Pokémon..." @bind="SearchTerm" @oninput="HandleSearch" />
<!-- Scrollable Pokémon Grid --> <!-- Scrollable Pokémon Grid -->
<div class="row pokemon-grid"> <div class="row pokemon-grid pt-1">
@foreach (var pokemon in FilteredPokemon) @foreach (var pokemon in FilteredPokemon)
{ {
bool isSelected = SelectedPokemon?.Id == pokemon.Id; bool isSelected = SelectedPokemon?.Id == pokemon.Id;
<div class="col-6 col-md-3 mb-3"> <div class="col-6 col-md-3 mb-3">
<div class="card pokemon-card small-card @(isSelected ? "border-primary border-2 shadow" : "border-2 border-white")" <div class="card pokemon-card small-card @(isSelected ? "border-primary border-2 shadow" : "border-2 border-white") rounded rounded-3"
@onclick="() => SelectPokemon(pokemon)"> @onclick="() => SelectPokemon(pokemon)">
<img src="@pokemon.PokemonImageUrl" class="card-img-top" style="height: 50px; object-fit: contain;" /> <img src="@pokemon.PokemonImageUrl" class="card-img-top" style="height: 50px; object-fit: contain;" />
<div class="card-body p-2 text-center"> <div class="card-body p-2 text-center">
@ -21,7 +21,3 @@
} }
</div> </div>
</div> </div>
<style>
</style>

View File

@ -5,11 +5,13 @@
border-radius: 5% / 3.5%; border-radius: 5% / 3.5%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.pokemon-grid { .pokemon-grid {
flex: 1 1 auto; flex: 1 1 auto;
overflow-y: auto; overflow-y: auto;
align-content: flex-start;
} }
@ -19,7 +21,7 @@
} }
.pokemon-card:hover { .pokemon-card:hover {
transform: scale(1.05); transform: scale(1.13);
} }
.small-card { .small-card {

View File

@ -5,20 +5,17 @@
@attribute [StreamRendering] @attribute [StreamRendering]
@rendermode InteractiveServer @rendermode InteractiveServer
<div class="d-flex justify-content-end">
</div>
<!-- Table A: Desktop View--> <!-- Table A: Desktop View-->
<div class="container d-none d-md-block " style="height: 70vh;"> <div class="container d-none d-md-block" style="height: 70vh;">
<!-- Main UI --> <!-- Main UI -->
<div class="border-0 mt-4 mx-auto col-12 col-md-10 col-lg-8 pokemontable "> <div class="border-0 mt-4 mx-auto col-12 col-md-10 col-lg-8 pokemontable">
<!-- Table Header --> <!-- Table Header -->
<div class="row bg-secondary bg-gradient py-3 border-0"> <div class="row bg-secondary bg-gradient py-3 border-0 rounded-top">
<div class="d-flex align-items-center justify-content-between w-100 position-relative px-3"> <div class="d-flex align-items-center justify-content-between w-100 position-relative px-3">
<!-- Left: Search --> <!-- Left: Search -->
<input class="form-control w-25 me-3" <input class="form-control w-25 me-3 rounded rounded-5"
placeholder="Search Pokémon..." placeholder="Search Pokémon..."
@bind="SearchTerm" @bind="SearchTerm"
@oninput="HandleSearch" /> @oninput="HandleSearch" />
@ -33,7 +30,7 @@
<div class="badge bg-info"> <div class="badge bg-info">
<p class="statText mb-0">@(pokemons.Count()) Pokémon</p> <p class="statText mb-0">@(pokemons.Count()) Pokémon</p>
</div> </div>
<PokemonDownload _Pokemon="pokemons" /> <PokemonDownloadButton _Pokemon="pokemons" />
</div> </div>
</div> </div>
@ -43,8 +40,8 @@
<div class="tableFixHead d-flex flex-column justify-content-start bg-secondary table-responsive row "> <div class="tableFixHead d-flex flex-column justify-content-start bg-secondary table-responsive row ">
<table class="table table-borderless border-0 table-secondary table-striped align-middle"> <table class="table table-borderless border-0 table-secondary table-striped align-middle">
<!-- Table Head --> <!-- Table Head -->
<thead class=""> <thead>
<tr class=""> <tr>
<th class="text-white text-bg-info col-2 d-none d-md-table-cell" scope="col"></th> <th class="text-white text-bg-info col-2 d-none d-md-table-cell" scope="col"></th>
<th class="text-white text-bg-info col-1" scope="col">#</th> <th class="text-white text-bg-info col-1" scope="col">#</th>
<th class="text-white text-bg-info col-2" scope="col">Pokémon</th> <th class="text-white text-bg-info col-2" scope="col">Pokémon</th>
@ -68,7 +65,7 @@
else else
{ {
<!-- Table Body --> <!-- Table Body -->
<tbody class=""> <tbody>
<tr></tr> <tr></tr>
@if (FilteredPokemon != null && FilteredPokemon.Any()) @if (FilteredPokemon != null && FilteredPokemon.Any())
{ {
@ -104,7 +101,7 @@
</td> </td>
<!-- Section 2: Pokemon # --> <!-- Section 2: Pokemon # -->
<th class="" scope="row" style="cursor: default;">@pokemon.PokemonId</th> <th scope="row" style="cursor: default;">@pokemon.PokemonId</th>
<!-- Section 3: Pokemon Name --> <!-- Section 3: Pokemon Name -->
@ -114,21 +111,21 @@
<!-- Section 4: Pokemon Type --> <!-- Section 4: Pokemon Type -->
<td class=""> <td>
<div class="d-flex justify-content-center"> <div class="d-flex justify-content-center">
<img src="@GetTypeImageUrl(pokemon.PokemonType)" style="width:36px; height:36px;" /> <img src="@GetTypeImageUrl(pokemon.PokemonType)" style="width:36px; height:36px;" />
</div> </div>
</td> </td>
<!-- Section 5: Sleep Type --> <!-- Section 5: Sleep Type -->
<td class="" style=""> <td>
<div class="d-flex justify-content-center "> <div class="d-flex justify-content-center ">
<PokemonBadge BadgeItem="@pokemon.SleepType" /> <PokemonBadge BadgeItem="@pokemon.SleepType" />
</div> </div>
</td> </td>
<!-- Section 6: Speciality --> <!-- Section 6: Speciality -->
<td class="" style=""> <td>
<div class="d-flex justify-content-center"> <div class="d-flex justify-content-center">
<PokemonBadge BadgeItem="@pokemon.Speciality" /> <PokemonBadge BadgeItem="@pokemon.Speciality" />
@ -137,10 +134,10 @@
@if (adminToggle) @if (adminToggle)
{ {
<td class="" style=""> <td>
<div class="d-flex justify-content-center"> <div class="d-flex justify-content-center">
<PokemonEditButton PokemonId="@pokemon.PokemonId" /> <PokemonEditButton PokemonId="@pokemon.PokemonId" />
@*<button @onclick="() => ConfirmDelete(pokemon.Id)">Delete</button>*@
</div> </div>
</td> </td>
} }
@ -203,10 +200,10 @@
@foreach (var pokemon in pokemons) @foreach (var pokemon in pokemons)
{ {
<tr class="border-0"> <tr class="border-0">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center" style="border: 1px dashed hotpink;">
<!-- Pokemon Image --> <!-- Pokemon Image -->
<div class="me-3"> <div class="me-3" style="border: 1px dashed hotpink;">
<div class="flip-container-sm" @onclick="() => ToggleImage(pokemon.Id)"> <div class="flip-container-sm" @onclick="() => ToggleImage(pokemon.Id)">
<div class="flipper-sm @(isShiny[pokemon.Id] ? "flipped" : "")"> <div class="flipper-sm @(isShiny[pokemon.Id] ? "flipped" : "")">
<img class="front img-fluid" src="@pokemon.PokemonImageUrl" /> <img class="front img-fluid" src="@pokemon.PokemonImageUrl" />
@ -218,7 +215,7 @@
</div> </div>
</div> </div>
<div class=""> <div style="border: 1px dashed hotpink;">
<!-- Number and Name --> <!-- Number and Name -->
<h5> <h5>
@pokemon.PokemonId - @pokemon.PokemonId -

View File

@ -25,7 +25,8 @@ else
<div class="mx-1 align-content-center"> <div class="mx-1 align-content-center">
<div class="addcard"> <div class="addcard">
<PokemonAddForm <PokemonForm
formUse="ADD"
OnPokemonReady="ReceivePokemon1" OnPokemonReady="ReceivePokemon1"
mostRecentForm=false mostRecentForm=false
/> />
@ -44,7 +45,8 @@ else
{ {
<div class="mx-1 align-content-center"> <div class="mx-1 align-content-center">
<div class="addcard"> <div class="addcard">
<PokemonAddForm OnPokemonReady="ReceivePokemon2" <PokemonForm OnPokemonReady="ReceivePokemon2"
formUse="ADD"
RemoveForm="TogglePokemon2FormView" RemoveForm="TogglePokemon2FormView"
mostRecentForm="@pokemon2FormView" /> mostRecentForm="@pokemon2FormView" />
</div> </div>
@ -58,14 +60,16 @@ else
{ {
<div class="mx-1 align-content-center"> <div class="mx-1 align-content-center">
<div class="addcard"> <div class="addcard">
<PokemonAddForm OnPokemonReady="ReceivePokemon2" <PokemonForm OnPokemonReady="ReceivePokemon2"
formUse="ADD"
RemoveForm="TogglePokemon2FormView" RemoveForm="TogglePokemon2FormView"
mostRecentForm="@pokemon2FormView" /> mostRecentForm="@pokemon2FormView" />
</div> </div>
</div> </div>
<div class="mx-1 align-content-center"> <div class="mx-1 align-content-center">
<div class="addcard"> <div class="addcard">
<PokemonAddForm OnPokemonReady="ReceivePokemon3" <PokemonForm OnPokemonReady="ReceivePokemon3"
formUse="ADD"
RemoveForm="TogglePokemon3FormView" RemoveForm="TogglePokemon3FormView"
mostRecentForm="@pokemon3FormView" /> mostRecentForm="@pokemon3FormView" />
</div> </div>
@ -76,7 +80,7 @@ else
</div> </div>
</div> </div>
<div class="d-flex justify-content-center"> <div class="d-flex justify-content-center pt-3">
<div class="btn-group"> <div class="btn-group">
<button @onclick="@HandleAdd" class="btn btn-primary rounded">Add Pokemon</button> <button @onclick="@HandleAdd" class="btn btn-primary rounded">Add Pokemon</button>
</div> </div>

View File

@ -60,32 +60,32 @@ namespace Portfolio.WebUI.Server.Components.Pages.Pokemon_Pages
*/ */
if (!pokemon2FormView && !pokemon3FormView) if (!pokemon2FormView && !pokemon3FormView)
{ {
if(IsComplete(pokemon1)) //if(IsComplete(pokemon1))
{ //{
await HandleSubmit(pokemon1); await HandleSubmit(pokemon1);
Navigation.NavigateTo("/pokemon"); Navigation.NavigateTo("/pokemon");
} //}
} }
else if (pokemon2FormView) else if (pokemon2FormView)
{ {
if (IsComplete(pokemon1) && IsComplete(pokemon2)) //if (IsComplete(pokemon1) && IsComplete(pokemon2))
{ //{
await HandleSubmit(pokemon1); await HandleSubmit(pokemon1);
await HandleSubmit(pokemon2); await HandleSubmit(pokemon2);
Navigation.NavigateTo("/pokemon"); Navigation.NavigateTo("/pokemon");
} // }
} }
else if (pokemon3FormView) else if (pokemon3FormView)
{ {
if (IsComplete(pokemon1) && IsComplete(pokemon2) && IsComplete(pokemon3)) //if (IsComplete(pokemon1) && IsComplete(pokemon2) && IsComplete(pokemon3))
{ //{
await HandleSubmit(pokemon1); await HandleSubmit(pokemon1);
await HandleSubmit(pokemon2); await HandleSubmit(pokemon2);
await HandleSubmit(pokemon3); await HandleSubmit(pokemon3);
Navigation.NavigateTo("/pokemon"); Navigation.NavigateTo("/pokemon");
} //}
} }
} }

View File

@ -1,10 +1,5 @@
.addcard { .addcard {
width: 100%; width: 100%;
max-width: 30rem; max-width: 25rem;
flex: 0 0 auto; /* prevent flex shrink/grow */
display: flex;
justify-content: center;
align-items: center;
padding: 0.5rem;
} }

View File

@ -1,7 +1,7 @@
@page "/pokemonsleep/edit/{id:int}" @page "/pokemonsleep/edit/{id:int}"
@inject IPokemonService PokemonService @inject IPokemonService PokemonService
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject IJSRuntime JSRuntime @inject IJSRuntime JS
@attribute [StreamRendering] @attribute [StreamRendering]
@rendermode InteractiveServer @rendermode InteractiveServer
@ -12,122 +12,33 @@
@if (pokemon == null) @if (pokemon == null)
{ {
<p><em>Loading...</em></p> <Loading />
} }
else else
{ {
<!-- Total Componenet-->
<div class="w-50 mt-3 m-auto bg-info edit-container">
<!-- Header --> <div class="container mx-0 px-0">
<div class="card-header bg-secondary ml-0 py-3 w-100 "> <div class="row mt-5">
<div class="row"> <div class="d-flex justify-content-evenly h-100 p-0">
<div class="col-12 text-center">
<h2 class="text-info">Edit Pokémon</h2> <div class="mx-1 align-content-center">
<div class="addcard">
<PokemonForm
formUse="EDIT"
OnPokemonReady="ReceivePokemon"
RemoveForm="Cancel"
mostRecentForm=true
PokemonToEdit="pokemon"
/>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<div class="d-flex justify-content-center">
<div class="btn-group">
<button @onclick="@HandleSubmit" class="btn btn-primary rounded">Edit Pokemon</button>
</div>
</div>
<!-- Body -->
<div class="p-3 w-100 flex-column ">
<EditForm class="col mb-3" Model="pokemon" OnValidSubmit="HandleSubmit">
<DataAnnotationsValidator />
<!-- Dex Number and Name -->
<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 />
</div>
</div>
<!-- Pokemon Type, Sleep Type, and Speciality -->
<div class="row">
<!-- Pokemon Type -->
<div class="col mb-3 m-auto">
<label for="PokemonType" class="form-label">Pokemon Type</label>
<InputSelect id="PokemonType" @bind-Value="pokemon.PokemonType" class="form-select">
<option dsabled value="">Select Type</option>
<option value="Grass">Grass</option>
<option value="Fire">Fire</option>
<option value="Water">Water</option>
<option value="Normal">Normal</option>
<option value="Flying">Flying</option>
<option value="Bug">Bug</option>
<option value="Poison">Poison</option>
<option value="Electric">Electric</option>
<option value="Ground">Ground</option>
<option value="Rock">Rock</option>
<option value="Ice">Ice</option>
<option value="Steel">Steel</option>
<option value="Fighting">Fighting</option>
<option value="Psychic">Psychic</option>
<option value="Dark">Dark</option>
<option value="Fairy">Fairy</option>
<option value="Ghost">Ghost</option>
<option value="Dragon">Dragon</option>
</InputSelect>
</div>
<!-- Sleep Type -->
<div class="col 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>
<!-- Speciality-->
<div class="col 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>
</div>
<!-- Variation Check -->
<div class="row">
<div class="input-group mb-3">
<!-- Variation Check -->
<div class=" d-inline-flex">
<InputCheckbox id="IsVariation" @bind-Value="pokemon.IsVariation" @onclick="@Toggle" class="form-check-input checkbox-styling p-3 mt-1" />
<span for="IsVariation" class="input-group-text m-1">Variation?</span>
</div>
<!-- Variation Region Input -->
<div class="w-75 mx-2 mt-1">
<InputText placeholder="What Variant? (Alolan, Paldean)" hidden="@HideLabel" id="VariationName" @bind-Value="pokemon.VariationName" class="form-control" />
</div>
</div>
</div>
<!-- Image URL Input -->
<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>
<!-- Shiny Image URL Input -->
<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>
<!-- Flavor Text Input -->
<div class="row mb-3 m-auto">
<label for="FlavorText" class="form-label">Flavor Text</label>
<InputText id="FlavorText" @bind-Value="pokemon.FlavorText" class="form-control" />
</div>
<!-- Form Buttons -->
<div class="d-flex justify-content-between">
<button type="button" class="btn btn-secondary mb-3" @onclick="Cancel">Cancel</button>
<button type="submit" class="btn btn-primary mb-3">Save Changes</button>
</div>
</EditForm>
</div>
</div>
} }

View File

@ -14,16 +14,28 @@ namespace Portfolio.WebUI.Server.Components.Pages.Pokemon_Pages
pokemon = await PokemonService.GetPokemonByIdAsync(Id); pokemon = await PokemonService.GetPokemonByIdAsync(Id);
} }
private async Task ReceivePokemon(Pokemon p)
{
// Save received pokemon as Pokemon 1
pokemon = p;
// Server console (Blazor Server)
Console.WriteLine($"[Pokemon 1] #{pokemon.PokemonId} {pokemon.PokemonName} | {pokemon.PokemonType} | {pokemon.SleepType} | {pokemon.Speciality} | Var:{pokemon.IsVariation} {pokemon.VariationName}");
// Browser console
await JS.InvokeVoidAsync("console.log", "Pokemon 1:", pokemon);
}
private async Task HandleSubmit() private async Task HandleSubmit()
{ {
await PokemonService.UpdatePokemonAsync(pokemon); await PokemonService.UpdatePokemonAsync(pokemon);
//Navigation.NavigateTo("/pokemonsleep"); //Navigation.NavigateTo("/pokemonsleep");
await JSRuntime.InvokeVoidAsync("history.back"); await JS.InvokeVoidAsync("history.back");
} }
private async void Cancel() private async void Cancel()
{ {
await JSRuntime.InvokeVoidAsync("history.back"); await JS.InvokeVoidAsync("history.back");
} }

View File

@ -5,3 +5,13 @@
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(255,255, 255, 0.19); box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(255,255, 255, 0.19);
border-radius: 15px; border-radius: 15px;
} }
.addcard {
width: 100%;
max-width: 30rem;
flex: 0 0 auto; /* prevent flex shrink/grow */
display: flex;
justify-content: center;
align-items: center;
padding: 0.5rem;
}