first commit

This commit is contained in:
2026-03-22 00:29:34 +01:00
commit c16b4e3933
1729 changed files with 162013 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
<Router AppAssembly="@typeof(App).Assembly" NotFoundPage="typeof(Pages.NotFound)">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</Router>
+7
View File
@@ -0,0 +1,7 @@
namespace Webshop.App
{
public class ApplicationState
{
public int NumberOfMessages { get; set; } = 0;
}
}
+19
View File
@@ -0,0 +1,19 @@
<div class="col">
<div class="card rounded-3 employee-card">
<div class="card-header py-3">
<h4>@Employee.FirstName @Employee.LastName</h4>
</div>
<div class="card=body employee-card text-center">
<div class="mb-3">
<img src="@Employee.ImageName" class="employee-img" />
</div>
<div class="container">
<div class="row justify-content-center">
<button class="btn btn-primary btn-sm mb-1 col-auto" @onclick="@(async () => await EmployeeQuickViewClicked.InvokeAsync(Employee))">Quick View</button>
<a class="btn btn-secondary btn-sm mb-1 ms-1 col-auto" href="@($"/employeedetail/{Employee.EmployeeId}")">Details</a>
<a class="btn btn-secondary btn-sm mb-1 ms-1 col-auto" href="@($"/employeeedit/{Employee.EmployeeId}")">Edit</a>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,21 @@
using BethanysPieShopHRM.Shared.Domain;
using Microsoft.AspNetCore.Components;
namespace Webshop.App.Components
{
public partial class EmployeeCard
{
[Parameter]
public Employee Employee { get; set; } = default! ;
[Parameter]
public EventCallback<Employee> EmployeeQuickViewClicked { get; set; }
protected override void OnInitialized()
{
if (string.IsNullOrEmpty(Employee.LastName))
{
throw new Exception("Last Name can't be empty");
}
}
}
}
@@ -0,0 +1,2 @@
<h4>@MessageCount message(s)</h4>
@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Components;
namespace Webshop.App.Components
{
public partial class InboxCounter
{
private int MessageCount;
[Inject]
public ApplicationState? ApplicationState { get; set; }
protected override void OnInitialized()
{
MessageCount = new Random().Next(10);
ApplicationState.NumberOfMessages = MessageCount;
}
}
}
@@ -0,0 +1,3 @@
<div class="profile-picture">
@ChildContent
</div>
@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Components;
namespace Webshop.App.Components
{
public partial class ProfilePicture
{
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
}
@@ -0,0 +1,56 @@
@if (_employee != null)
{
<div class="modal fade show d-block" id="exampleModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="titleLabel">Employee Quick View</h5>
<button @onclick="Close" type="button" class="close btn btn-lg" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="col row">
<div class="col-12 col-sm-8">
<div class="form-group row">
<label class="col-sm-5 col-form-label">Employee ID</label>
<div class="col-sm-7">
<label type="text" class="form-control-plaintext">@_employee.EmployeeId</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-5 col-form-label">First name</label>
<div class="col-sm-7">
<label type="text" readonly class="form-control-plaintext">@_employee.FirstName</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-5 col-form-label">Last name</label>
<div class="col-sm-7">
<label type="text" readonly class="form-control-plaintext">@_employee.LastName</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-5 col-form-label">Birthdate</label>
<div class="col-sm-7">
<label type="text" readonly class="form-control-plaintext">@_employee.BirthDate.ToShortDateString()</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-5 col-form-label">Email</label>
<div class="col-sm-7">
<label type="text" readonly class="form-control-plaintext">@_employee.Email</label>
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-outline-primary" @onclick="Close">Close</button>
</div>
</div>
</div>
</div>
}
@@ -0,0 +1,23 @@
using BethanysPieShopHRM.Shared.Domain;
using Microsoft.AspNetCore.Components;
namespace Webshop.App.Components
{
public partial class QuickViewPopup
{
[Parameter]
public Employee Employee { get; set; } = default!;
private Employee? _employee;
protected override void OnParametersSet()
{
base.OnParametersSet();
_employee = Employee;
}
public void Close()
{
_employee = null;
}
}
}
@@ -0,0 +1,7 @@
<h3>Employee Counter</h3>
<h3>There are currently @EmployeeCounter employees working at Bethany's Pie Shop!</h3>
<br />
<br />
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Components;
using Webshop.App.Services;
namespace Webshop.App.Components.Widgets
{
public partial class EmployeeCountWidget
{
[Inject]
public IEmployeeDataService EmployeeDataService { get; set; } = default!;
public int EmployeeCounter { get; set; }
protected async override Task OnInitializedAsync()
{
EmployeeCounter = await RetrieveNumberOfEmployees();
}
public async Task<int> RetrieveNumberOfEmployees()
{
var employees = await EmployeeDataService.GetAllEmployees();
return employees.Count();
}
}
}
@@ -0,0 +1,13 @@
<h3>Inbox</h3>
@if (@MessageCount > 0)
{
<h4>You currently have @MessageCount questions from employees!</h4>
}
else
{
<h4>No questions from employees! All good!</h4>
}
<br />
<br />
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Components;
namespace Webshop.App.Components.Widgets
{
public partial class InboxWidget
{
[Inject]
public ApplicationState? ApplicationState { get; set; }
public int MessageCount { get; set; } = 0;
protected override void OnInitialized()
{
MessageCount = ApplicationState.NumberOfMessages;
}
}
}
+42
View File
@@ -0,0 +1,42 @@
@inherits LayoutComponentBase
<nav class="navbar navbar-expand-lg navbar-dark fixed-top bg-primary px-5"
aria-label="Bethany's Pie Shop navigation header">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<img src="images/bethanylogo.png" width="151" height="47" class="d-inline-block align-top mb-2 mt-1"
alt="Bethany's Pie Shop Logo">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse"
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<h3 class="navbar-brand me-auto ms-5">Bethany's Pie Shop HRM </h3>
<ul class="navbar-nav mb-2 mb-lg-0">
<li class="nav-item">
<div class="nav-link">
<InboxCounter></InboxCounter>
</div>
</li>
<li class="nav-item">
<div class="nav-link">
</div>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid h-100">
<div class="row h-100">
<NavMenu></NavMenu>
<div id="page-content-wrapper">
@Body
</div>
</div>
</div>
+81
View File
@@ -0,0 +1,81 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row:not(.auth) {
display: none;
}
.top-row.auth {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
+19
View File
@@ -0,0 +1,19 @@
<div class="sidebar bg-light">
<div class="pt-2 h-100">
<ProfilePicture>
<img class="img-fluid rounded-circle" src="/images/profilePicture.jpg"/>
</ProfilePicture>
<div class="menu list-group list-group-flush pt-5">
<NavLink class="list-group-item list-group-item-action bg-light" href="/" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
<NavLink class="list-group-item list-group-item-action bg-light" href="/employeeoverview">
<span class="oi oi-people" aria-hidden="true"></span> Employees
</NavLink>
<NavLink class="list-group-item list-group-item-action bg-light" href="/employeeedit">
<span class="oi oi-people" aria-hidden="true"></span> Create a new employee
</NavLink>
</div>
</div>
</div>
+83
View File
@@ -0,0 +1,83 @@
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
}
.top-row {
min-height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.collapse {
/* Never collapse the sidebar for wide screens */
display: block;
}
.nav-scrollable {
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}
+121
View File
@@ -0,0 +1,121 @@
@page "/employeedetail/{EmployeeId:int}"
@if(Employee == null)
{
<div>Loading...</div>
} else
{
<section class="employee-detail">
<h1 class="page-title">Details for @Employee.FirstName @Employee.LastName</h1>
<br />
<div class="col-12 row">
<div class="col-2 text-center">
<img src="@($"https://gillcleerenpluralsight.blob.core.windows.net/person/{Employee.EmployeeId}.jpg")" class="img-fluid rounded-circle employee-detail-img" />
</div>
<div class="col-10 row">
<div class="col-12 col-xxl-8">
<div class="form-group row">
<label class="col-sm-4 col-form-label">Employee ID</label>
<div class="col-sm-8">
<label type="text" class="form-control-plaintext">@Employee.EmployeeId</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">First name</label>
<div class="col-sm-8">
<label type="text" readonly class="form-control-plaintext">@Employee.FirstName</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Last name</label>
<div class="col-sm-8">
<label type="text" readonly class="form-control-plaintext">@Employee.LastName</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Birthdate</label>
<div class="col-sm-8">
<label type="text" readonly class="form-control-plaintext">@Employee.BirthDate.ToShortDateString()</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Email</label>
<div class="col-sm-8">
<label type="text" readonly class="form-control-plaintext">@Employee.Email</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Street</label>
<div class="col-sm-8">
<label type="text" readonly class="form-control-plaintext">@Employee.Street</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Zip</label>
<div class="col-sm-8">
<label type="text" readonly class="form-control-plaintext">@Employee.Zip</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">City</label>
<div class="col-sm-8">
<label type="text" readonly class="form-control-plaintext">@Employee.City</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Phone number</label>
<div class="col-sm-8">
<label type="text" readonly class="form-control-plaintext">@Employee.PhoneNumber</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Gender</label>
<div class="col-sm-8">
<label type="text" readonly class="form-control-plaintext">@Employee.Gender</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Smoker</label>
<div class="col-sm-8">
@if (Employee.Smoker)
{
<label type="text" readonly class="form-control-plaintext">Yes</label>
}
else
{
<label type="text" readonly class="form-control-plaintext">No</label>
}
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Joined us</label>
<div class="col-sm-8">
<label type="text" readonly class="form-control-plaintext">@Employee.JoinedDate?.ToShortDateString()</label>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label">Left us</label>
<div class="col-sm-8">
<label type="text" readonly class="form-control-plaintext">@Employee.ExitDate?.ToShortDateString()</label>
</div>
</div>
</div>
<div class="col-12 col-xxl-4">
</div>
</div>
</div>
</section>
}
@@ -0,0 +1,33 @@
using BethanysPieShopHRM.Shared.Domain;
using Microsoft.AspNetCore.Components;
using Webshop.App.Services;
namespace Webshop.App.Pages
{
public partial class EmployeeDetails
{
[Inject]
public NavigationManager NavigationManager { get; set; } = default!;
[Inject]
public IEmployeeDataService? EmployeeDataService { get; set; }
[Parameter]
public int EmployeeId { get; set; }
public Employee? Employee { get; set; } = default;
protected async override Task OnInitializedAsync()
{
Employee = await RetrieveEmployee();
}
public async Task<Employee?> RetrieveEmployee()
{
var employeeFound = await EmployeeDataService.GetEmployeeDetails(EmployeeId);
if(employeeFound == null)
{
NavigationManager.NavigateTo("/not-found");
}
return employeeFound;
}
}
}
+228
View File
@@ -0,0 +1,228 @@
@page "/employeeEdit"
@page "/employeeEdit/{EmployeeId:int}"
@using BethanysPieShopHRM.Shared.Domain
@if (!Saved)
{
<section id="EmployeeEditSectionForm" class="employee-edit">
@if (Employee != null)
{
<h1 class="page-title">Details de @Employee.FirstName @Employee.LastName</h1>
<EditForm Model="Employee" OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
@if (!EmployeeId.HasValue)
{
<div class="row mb-3">
<label for="image" class="col-12 col-sm-3">Select Image:</label>
<InputFile OnChange="OnInputFileChange" />
</div>
}
<div class="row mb-3">
<label for="lastName" class="col-form-label col-md-3">Last name: </label>
<div class="col-md-8">
<InputText id="lastName" class="form-control col-md-8" @bind-Value="@Employee.LastName" placeholder="Enter last name"></InputText>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.LastName)" />
</div>
</div>
<div class="row mb-3">
<label for="firstName" class="col-form-label col-md-3">First name: </label>
<div class="col-md-8">
<InputText id="firstName" class="form-control col-md-8" @bind-Value="@Employee.FirstName" placeholder="Enter first name"></InputText>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.FirstName)" />
</div>
</div>
<div class="row mb-3">
<label for="birthdate" class="col-form-label col-md-3">Birthdate: </label>
<div class="col-md-8">
<InputDate id="birthdate" class="form-control col-md-8" @bind-Value="@Employee.BirthDate" placeholder="Enter birthdate"></InputDate>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.BirthDate)" />
</div>
</div>
<div class="row mb-3">
<label for="email" class="col-form-label col-md-3">Email: </label>
<div class="col-md-8">
<InputText id="email" class="form-control col-md-8" @bind-Value="@Employee.Email" placeholder="Enter email"></InputText>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.Email)" />
</div>
</div>
<div class="row mb-3">
<label for="street" class="col-form-label col-md-3">Street: </label>
<div class="col-md-8">
<InputText id="street" class="form-control col-md-8" @bind-Value="@Employee.Street" placeholder="Enter street"></InputText>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.Street)" />
</div>
</div>
<div class="row mb-3">
<label for="zip" class="col-form-label col-md-3">Zip code: </label>
<div class="col-md-8">
<InputText id="zip" class="form-control col-md-8" @bind-Value="@Employee.Zip" placeholder="Enter zip code"></InputText>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.Zip)" />
</div>
</div>
<div class="row mb-3">
<label for="city" class="col-form-label col-md-3">City: </label>
<div class="col-md-8">
<InputText id="city" class="form-control col-md-8" @bind-Value="@Employee.City" placeholder="Enter city"></InputText>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.City)" />
</div>
</div>
<div class="row mb-3">
<label for="country" class="col-form-label col-md-3">Country: </label>
<div class="col-md-8">
<InputSelect id="country" class="form-control col-md-8" @bind-Value="Employee.CountryId">
@foreach (var country in Countries)
{
<option value="@country.CountryId">@country.Name</option>
}
</InputSelect>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.CountryId)" />
</div>
</div>
<div class="row mb-3">
<label for="phonenumber" class="col-form-label col-md-3">Phone number: </label>
<div class="col-md-8">
<InputText id="phonenumber" class="form-control col-md-8" @bind-Value="@Employee.PhoneNumber" placeholder="Enter phone number"></InputText>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.PhoneNumber)" />
</div>
</div>
<div class="row mb-3">
<label for="longitude" class="col-form-label col-md-3">Longitude: </label>
<div class="col-md-8">
<InputNumber id="longitude" class="form-control col-md-8" @bind-Value="@Employee.Longitude"></InputNumber>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.Longitude)" />
</div>
</div>
<div class="row mb-3">
<label for="latitude" class="col-form-label col-md-3">Latitude: </label>
<div class="col-md-8">
<InputNumber id="latitude" class="form-control col-md-8" @bind-Value="@Employee.Latitude"></InputNumber>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.Latitude)" />
</div>
</div>
<div class="row mb-3">
<div class="col-md-8 offset-md-3">
<div class="form-check">
<InputCheckbox id="smoker" class="form-check-input" @bind-Value="@Employee.Smoker"></InputCheckbox>
<label class="form-check-label" for="smoker">
Smoker
</label>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.Smoker)" />
</div>
</div>
</div>
<div class="row mb-3">
<label for="jobcategory" class="col-form-label col-md-3">Job category: </label>
<div class="col-md-8">
<InputSelect id="jobcategory" class="form-select col-md-8" @bind-Value="@Employee.JobCategoryId">
@foreach (var jobCategory in JobCategories)
{
<option value="@jobCategory.JobCategoryId">@jobCategory.JobCategoryName</option>
}
</InputSelect>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.JobCategoryId)" />
</div>
</div>
<div class="row mb-3">
<InputRadioGroup Name="genderRadioGroup" @bind-Value="@Employee.Gender">
<label class="col-form-label col-md-3">Gender: </label>
<div class="col-md-8">
@foreach (var gender in (Gender[])Enum.GetValues(typeof(Gender)))
{
<InputRadio Name="genderRadioGroup" Value="gender" class="form-check-input" />
<label class="form-check-label">
@gender
</label>
<br />
}
</div>
</InputRadioGroup>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.Gender)" />
</div>
<div class="row mb-3">
<label for="maritalstatus" class="col-form-label col-md-3">Marital status: </label>
<div class="col-md-8">
<InputSelect id="maritalstatus" class="form-select col-md-8" @bind-Value=@Employee.MaritalStatus>
<option value="@(MaritalStatus.Single)">Single</option>
<option value="@(MaritalStatus.Married)">Married</option>
<option value="@(MaritalStatus.Other)">Other</option>
</InputSelect>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.MaritalStatus)" />
</div>
</div>
<div class="row mb-3">
<label for="joineddate" class="col-form-label col-md-3">Joined on: </label>
<div class="col-md-8">
<InputDate id="joineddate" class="form-control" @bind-Value="@Employee.JoinedDate" placeholder="Enter date joined"></InputDate>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.JoinedDate)" />
</div>
</div>
<div class="row mb-3">
<label for="exitdate" class="col-form-label col-md-3">Left on: </label>
<div class="col-md-8">
<InputDate id="exitdate" class="form-control" @bind-Value="@Employee.ExitDate" placeholder="Enter exit date"></InputDate>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.ExitDate)" />
</div>
</div>
<div class="row mb-3">
<label for="comment" class="col-form-label col-md-3">Comment: </label>
<div class="col-md-8">
<InputTextArea id="comment" class="form-control" @bind-Value="@Employee.Comment" placeholder="Enter comment"></InputTextArea>
<ValidationMessage class="offset-md-3 col-md-8 text-danger" For="@(() => Employee.Comment)" />
</div>
</div>
<button class="btn btn-secondary" @onclick="BackToOverview">Cancel</button>
<button class="btn btn-primary me-1" type="submit">Submit</button>
@if(EmployeeId.HasValue){
<button class="btn btn-danger" @onclick="DeleteEmployee">Delete</button>
}
</EditForm>
}
else
{
<div>Loading...</div>
}
</section>
} else
{
<div class="alert @StatusClass">@Message</div>
<button class="btn btn-secondary" @onclick="BackToOverview">Back to overview</button>
}
+124
View File
@@ -0,0 +1,124 @@
using BethanysPieShopHRM.Shared.Domain;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Webshop.App.Services;
namespace Webshop.App.Pages
{
public partial class EmployeeEdit
{
[Inject]
public IEmployeeDataService EmployeeDataService { get; set; } = default!;
[Inject]
public ICountryDataService CountryDataService { get; set; } = default!;
[Inject]
public IJobCategoryDataService JobCategoryDataService { get; set; } = default!;
[Inject]
public NavigationManager NavigationManager { get; set; } = default!;
[Parameter]
public int? EmployeeId { get; set; }
public Employee? Employee { get; set; }
public IEnumerable<Country> Countries { get; set; } = new List<Country>();
public IEnumerable<JobCategory> JobCategories { get; set; } = new List<JobCategory>();
protected string Message = string.Empty;
protected string StatusClass = string.Empty;
protected bool Saved;
private IBrowserFile selectedFile;
private async Task<Employee?> GetEmployee(int employeeId)
{
return await EmployeeDataService.GetEmployeeDetails(employeeId);
}
private void OnInputFileChange(InputFileChangeEventArgs e)
{
selectedFile = e.File;
StateHasChanged();
}
protected async override Task OnInitializedAsync()
{
Countries = await CountryDataService.GetCountries();
JobCategories = await JobCategoryDataService.GetJobCategories();
Saved = false;
}
protected async override Task OnParametersSetAsync()
{
if (!EmployeeId.HasValue)
{
Employee = new Employee();
return;
}
var employee = await GetEmployee(EmployeeId.Value);
if (employee is null)
{
NavigationManager.NavigateTo("/not-found");
return;
}
Employee = employee;
}
protected async Task HandleValidSubmit()
{
Saved = false;
if (!EmployeeId.HasValue && Employee != null)
{
if (selectedFile != null)
{
var file = selectedFile;
Stream stream = file.OpenReadStream();
MemoryStream ms = new();
await stream.CopyToAsync(ms);
stream.Close();
Employee.ImageName = file.Name;
Employee.ImageContent = ms.ToArray();
}
var addedEmployee = await EmployeeDataService.AddEmployee(Employee);
if (addedEmployee != null)
{
StatusClass = "alert-success";
Message = "New employee added successfully.";
Saved = true;
}
else
{
StatusClass = "alert-danger";
Message = "Something went wrong adding the new employee. Please try again.";
Saved = false;
}
}
else if (Employee != null)
{
await EmployeeDataService.UpdateEmployee(Employee);
StatusClass = "alert-success";
Message = "Employee updated successfully.";
Saved = true;
}
}
protected async Task HandleInvalidSubmit()
{
StatusClass = "alert-danger";
Message = "validation errors";
}
protected async Task DeleteEmployee()
{
if(Employee != null && Employee.EmployeeId > 0)
{
await EmployeeDataService.DeleteEmployee(Employee.EmployeeId);
StatusClass = "alert-success";
Message = "Deleted successful";
}
}
protected void BackToOverview()
{
NavigationManager.NavigateTo("/employeeoverview");
}
}
}
+27
View File
@@ -0,0 +1,27 @@
@page "/employeeoverview"
<PageTitle>@Title</PageTitle>
<h2>Employee Overview</h2>
@if(Employees == null)
{
<div>Loading...</div>
} else
{
<QuickViewPopup Employee="_employeeSelected"></QuickViewPopup>
<div class="row">
@foreach (var employee in Employees)
{
<div class="col-12 col-sm-12 col-md-6 col-lg-4 col-xl-3">
<ErrorBoundary>
<ChildContent>
<EmployeeCard Employee="employee" EmployeeQuickViewClicked="ShowQuickViewPopup"></EmployeeCard>
</ChildContent>
<ErrorContent>
<p class="errorUI">Invalid Employee!</p>
</ErrorContent>
</ErrorBoundary>
</div>
}
</div>
}
@@ -0,0 +1,32 @@
using BethanysPieShopHRM.Shared.Domain;
using Microsoft.AspNetCore.Components;
using Webshop.App.Services;
namespace Webshop.App.Pages
{
public partial class EmployeeOverview
{
[Inject]
public NavigationManager NavigationManager { get; set; } = default!;
[Inject]
public IEmployeeDataService EmployeeDataService { get; set; } = default!;
public IEnumerable<Employee>? Employees { get; set; } = default;
private Employee? _employeeSelected;
private string Title = "Employee overview";
protected async override Task OnInitializedAsync()
{
Employees = await GetEmployees();
}
public void ShowQuickViewPopup(Employee employee)
{
_employeeSelected = employee;
}
public async Task<IEnumerable<Employee>> GetEmployees()
{
return await EmployeeDataService.GetAllEmployees();
}
}
}
+38
View File
@@ -0,0 +1,38 @@
@page "/"
<PageTitle>Salut</PageTitle>
<h1>Salut t'es ici c'est cool</h1>
@foreach(var widget in Widgets)
{
<DynamicComponent Type="widget"></DynamicComponent>
}
<div>
<h2>One way Data Binding</h2>
Employee First Name: <label>@Employee.FirstName</label>
Employee Last name: <label>@Employee.LastName</label>
</div>
<div>
<h2>One way data binding in form controls</h2>
Employee First Name:
<input type="text" value="@Employee.FirstName" />
Employee Last Name:
<input type="text" value="@Employee.LastName"/>
</div>
<div>
<h2>Two way data binding in form controls</h2>
Employee First Name:
<input type="text" @bind="@Employee.FirstName" />
Employee Last Name:
<input type="text" @bind="@Employee.LastName" />
</div>
<div>
<h2>Two way data binding in form controls on a different event</h2>
Employee First Name:
<input type="text" @bind-value="@Employee.FirstName" @bind-value:event="oninput" />
Employee Last Name:
<input type="text" @bind-value="@Employee.LastName" @bind-value:event="oninput" />
</div>
<button @onclick="ButtonClick" class="btn btn-primary">Click me</button>
+35
View File
@@ -0,0 +1,35 @@
using BethanysPieShopHRM.Shared.Domain;
using Webshop.App.Components.Widgets;
namespace Webshop.App.Pages
{
public partial class Home
{
public Employee Employee { get; set; }
public List<Type> Widgets { get; set; } = new List<Type>()
{
typeof(EmployeeCountWidget),
typeof(InboxWidget),
};
protected override Task OnInitializedAsync()
{
Employee = new Employee
{
FirstName = "Caca",
LastName = "Pipi"
};
return base.OnInitializedAsync();
}
public void ButtonClick()
{
Employee.FirstName = "ProutProut";
}
public void Test()
{
}
}
}
+5
View File
@@ -0,0 +1,5 @@
@page "/not-found"
@layout MainLayout
<h3>Not Found</h3>
<p>Sorry, the content you are looking for does not exist.</p>
+20
View File
@@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Webshop.App;
using Webshop.App.Services;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddHttpClient<IEmployeeDataService, EmployeeDataService>(
client => client.BaseAddress = new Uri("https://localhost:7039"));
builder.Services.AddHttpClient<ICountryDataService, CountryDataService>(
client => client.BaseAddress = new Uri("https://localhost:7039"));
builder.Services.AddHttpClient<IJobCategoryDataService, JobCategoryDataService>(
client => client.BaseAddress = new Uri("https://localhost:7039"));
builder.Services.AddScoped<ApplicationState>();
builder.Services.AddLocalStorageServices();
await builder.Build().RunAsync();
@@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5065",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7255;http://localhost:5065",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,24 @@
using BethanysPieShopHRM.Shared.Domain;
using System.Net.Http.Json;
namespace Webshop.App.Services
{
public class CountryDataService : ICountryDataService
{
private readonly HttpClient _httpClient;
public CountryDataService(
HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<IEnumerable<Country>> GetCountries() {
return await _httpClient.GetFromJsonAsync<IEnumerable<Country>>("/api/country");
}
public async Task<Country> GetCountryById(int countryId)
{
return await _httpClient.GetFromJsonAsync<Country>($"/api/country/{countryId}");
}
}
}
@@ -0,0 +1,45 @@
using BethanysPieShopHRM.Shared.Domain;
using System.Net.Http.Json;
namespace Webshop.App.Services
{
public class EmployeeDataService : IEmployeeDataService
{
private readonly HttpClient _httpClient;
public EmployeeDataService(
HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Employee?> AddEmployee(Employee employee)
{
var response = await _httpClient.PostAsJsonAsync<Employee>("/api/employee", employee);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadFromJsonAsync<Employee>();
}
return null;
}
public async Task DeleteEmployee(int EmployeeId)
{
await _httpClient.DeleteAsync($"/api/employee/{EmployeeId}");
}
public async Task<IEnumerable<Employee>> GetAllEmployees()
{
return await _httpClient.GetFromJsonAsync<IEnumerable<Employee>>("/api/employee");
}
public async Task<Employee> GetEmployeeDetails(int employeeId)
{
return await _httpClient.GetFromJsonAsync<Employee>($"api/employee/{employeeId}");
}
public async Task UpdateEmployee(Employee employee)
{
await _httpClient.PutAsJsonAsync<Employee>("/api/employee", employee);
}
}
}
@@ -0,0 +1,10 @@
using BethanysPieShopHRM.Shared.Domain;
namespace Webshop.App.Services
{
public interface ICountryDataService
{
public Task<IEnumerable<Country>> GetCountries();
public Task<Country> GetCountryById(int countryId);
}
}
@@ -0,0 +1,13 @@
using BethanysPieShopHRM.Shared.Domain;
namespace Webshop.App.Services
{
public interface IEmployeeDataService
{
Task<IEnumerable<Employee>> GetAllEmployees();
Task<Employee> GetEmployeeDetails(int employeeId);
Task<Employee?> AddEmployee(Employee employee);
Task UpdateEmployee(Employee employee);
Task DeleteEmployee(int EmployeeId);
}
}
@@ -0,0 +1,11 @@
using BethanysPieShopHRM.Shared.Domain;
namespace Webshop.App.Services
{
public interface IJobCategoryDataService
{
public Task<IEnumerable<JobCategory>> GetJobCategories();
public Task<JobCategory> GetJobCategoryById(int jobCategoryId);
}
}
@@ -0,0 +1,26 @@
using BethanysPieShopHRM.Shared.Domain;
using System.Net.Http.Json;
namespace Webshop.App.Services
{
public class JobCategoryDataService : IJobCategoryDataService
{
private readonly HttpClient _httpClient;
public JobCategoryDataService(
HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<IEnumerable<JobCategory>> GetJobCategories()
{
return await _httpClient.GetFromJsonAsync<IEnumerable<JobCategory>>("/api/jobcategory");
}
public async Task<JobCategory> GetJobCategoryById(int jobCategoryId)
{
return await _httpClient.GetFromJsonAsync<JobCategory>($"/api/jobcategory/{jobCategoryId}");
}
}
}
+21
View File
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.5" />
<PackageReference Include="Blazor.LocalStorage" Version="9.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BethanysPieShopHRM.Shared\BethanysPieShopHRM.Shared.csproj" />
</ItemGroup>
</Project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>https</ActiveDebugProfile>
</PropertyGroup>
</Project>
+13
View File
@@ -0,0 +1,13 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using Webshop.App
@using Webshop.App.Layout
@using Webshop.App.Components
+3
View File
@@ -0,0 +1,3 @@
{
"apiUrl": "https://localhost:7039"
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More