Skip to content

Commit

Permalink
Merge pull request #136 from martinjonsson01/97-editing-contract-data…
Browse files Browse the repository at this point in the history
…-from-admin-page

97 editing contract data from admin page
  • Loading branch information
martinjonsson01 authored May 11, 2022
2 parents 4abc9dc + 313632d commit 3e63173
Show file tree
Hide file tree
Showing 13 changed files with 341 additions and 141 deletions.
53 changes: 33 additions & 20 deletions src/Client/Pages/Admin/AdminPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,43 @@

<h2>Adminsida</h2>

<Tabs SelectedTab="@_selectedTab"
SelectedTabChanged="@OnSelectedTabChanged"
Justified="true"
Pills="true">
<Items>
<Tab Name="contracts">Hantera kontrakt</Tab>
<Tab Name="users">Hantera användare</Tab>
</Items>
<Content>
<TabPanel Name="contracts">
<ContractForm OnContractUploaded="AddContractToTable"/>
<ContractTable @ref="@_contractTable"/>
</TabPanel>
<TabPanel Name="users">
<UserForm OnUserAdded="AddUserToTable"/>
<UserTable @ref="@_userTable"/>
</TabPanel>
</Content>
</Tabs>
<!-- Extra div needed to style the Blazorise-components (see https://github.com/Megabit/Blazorise/issues/1599) -->
<div>
<Tabs SelectedTab="@_selectedTab"
SelectedTabChanged="@OnSelectedTabChanged"
Justified="true"
Pills="true"
TextColor="TextColor.Primary"
Class="tabs">
<Items>
<Tab Name="contracts">Hantera kontrakt</Tab>
<Tab Name="users">Hantera användare</Tab>
</Items>
<Content>
<TabPanel Name="contracts">
<ContractForm @ref="_contractForm" OnContractUploaded="AddContractToTable"/>
<ContractTable @ref="@_contractTable" ContractOpeningForEdit="@OnContractOpeningForEdit"/>
</TabPanel>
<TabPanel Name="users">
<UserForm OnUserAdded="AddUserToTable"/>
<UserTable @ref="@_userTable"/>
</TabPanel>
</Content>
</Tabs>
</div>

@code {

private string _selectedTab = "contracts";

// Null-forgiving operator used because these fields are set by the Blazor runtime using @ref
private ContractTable _contractTable = null!;
private UserTable _userTable = null!;
private ContractForm _contractForm = null!;

private void AddContractToTable(Contract contract)
{
_contractTable.Add(contract);
_contractTable.AddOrUpdate(contract);
}

private void AddUserToTable(User user)
Expand All @@ -46,4 +53,10 @@
_selectedTab = tabName;
return Task.CompletedTask;
}

private async Task OnContractOpeningForEdit(Contract contract)
{
await _contractForm.EditContractAsync(contract);
}

}
4 changes: 4 additions & 0 deletions src/Client/Pages/Admin/AdminPage.razor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
::deep .nav-link {
background: #e7e7e7;
color: var(--prodigo-dark-blue)
}
71 changes: 53 additions & 18 deletions src/Client/Pages/Admin/ContractForm.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
@using Blazorise.Markdown
@inject ILogger<AdminPage> _logger
@inject HttpClient _http
@inject IJSRuntime _js

<h4>Skapa nytt kontrakt</h4>
<h4 id="contract-form-title">Skapa nytt kontrakt</h4>

<EditForm Model="@_contract" OnSubmit="OnSubmit">

Expand Down Expand Up @@ -101,6 +102,38 @@

private const string MinFormHeight = "5rem";

/// <summary>
/// Populates the form with the values of the given <see cref="Contract"/>.
/// </summary>
/// <param name="contract">The data to edit.</param>
public async Task EditContractAsync(Contract contract)
{
await _js.InvokeVoidAsync("scrollToElement", "#contract-form-title");
_shouldRender = true;
_contract = contract;
await DownloadContractFiles();
}

private async Task DownloadContractFiles()
{
await DownloadContractFile(_contract.InspirationalImagePath, content => _inspirationalContent = content);
await DownloadContractFile(_contract.SupplierLogoImagePath, content => _supplierLogoContent = content);
await DownloadContractFile(_contract.AdditionalDocument, content => _additionalDocumentContent = content);
}

private async Task DownloadContractFile(string path, Action<MultipartFormDataContent> setContent)
{

var fileUri = new Uri(Path.Join(_http.BaseAddress?.ToString(), path[1..]), UriKind.Absolute);

HttpResponseMessage response = await _http.GetAsync(fileUri);
string mediaType = response.Content.Headers.ContentType?.MediaType ?? "images/jpeg";
Stream stream = await response.Content.ReadAsStreamAsync();

MultipartFormDataContent content = CreateFormDataContent(stream, fileUri.Segments.Last(), mediaType);
setContent(content);
}

private static Contract CreateEmptyContract()
{
return new Contract
Expand All @@ -126,34 +159,37 @@
{
_shouldRender = false;

content = new MultipartFormDataContent();
StreamContent fileContent;

const long bitsInAKilobyte = 1024;
const long kilobytesInAMegabyte = 1024;
const long maxFileSize = bitsInAKilobyte * kilobytesInAMegabyte * 100;

Stream fileStream = arg.File.OpenReadStream(maxFileSize);

content = CreateFormDataContent(fileStream, arg.File.Name, arg.File.ContentType);

_shouldRender = true;
}

private MultipartFormDataContent CreateFormDataContent(Stream fileStream, string fileName, string contentType)
{
StreamContent fileContent;

try
{
fileContent = new StreamContent(arg.File.OpenReadStream(maxFileSize));
fileContent = new StreamContent(fileStream);
}
catch (IOException ex)
{
_logger.LogInformation(
"{FileName} not uploaded: {Message}",
arg.File.Name, ex.Message);
return;
_logger.LogInformation("{FileName} not uploaded: {Message}", fileName, ex.Message);
throw;
}

fileContent.Headers.ContentType =
new MediaTypeHeaderValue(arg.File.ContentType);
fileContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);

content.Add(
fileContent,
"\"file\"",
arg.File.Name);
var content = new MultipartFormDataContent();
content.Add(fileContent, "\"file\"", fileName);

_shouldRender = true;
return content;
}

private async Task OnSubmit()
Expand Down Expand Up @@ -192,12 +228,11 @@
{
string json = JsonConvert.SerializeObject(_contract);
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await _http.PostAsync("/api/v1/contracts", content);
HttpResponseMessage response = await _http.PutAsync($"/api/v1/contracts/{_contract.Id}", content);
if (response.IsSuccessStatusCode)
{
await OnContractUploaded.InvokeAsync(_contract);
_contract = CreateEmptyContract();
}
}

}
29 changes: 25 additions & 4 deletions src/Client/Pages/Admin/ContractTable.razor
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@
<tr>
<th scope="col">Kontraktnamn</th>
<th scope="col">Företag</th>
<th scope="col">Redigera</th>
<th scope="col">Ta bort</th>
</tr>
</thead>

<tbody>
@foreach (Contract contract in contracts)
{
<ContractTableRow Contract=@contract OnContractRemoved=@Remove/>
<ContractTableRow @key="@contract.Id"
Contract=@contract
OnContractRemoved=@Remove
ContractOpeningForEdit="@(async () => await ContractOpeningForEdit.InvokeAsync(contract))"/>
}
</tbody>
</table>
Expand All @@ -28,18 +32,35 @@

@code {

private FetchData<List<Contract>> _dataFetcher = null!;
/// <summary>
/// Called when a <see cref="Contract"/> is being opened to be edited.
/// </summary>
[Parameter]
public EventCallback<Contract> ContractOpeningForEdit { get; set; } = EventCallback<Contract>.Empty;

/// <summary>
/// Adds a contract to the list.
/// </summary>
/// <param name="contract">The contract to add.</param>
public void Add(Contract contract)
public void AddOrUpdate(Contract contract)
{
_dataFetcher.Data?.Add(contract);
if (_dataFetcher.Data is null)
return;

if (_dataFetcher.Data.Any(other => other.Id == contract.Id))
{
_dataFetcher.Data.RemoveAll(toRemove => toRemove.Id == contract.Id);
_dataFetcher.Data.Add(contract);
}
else
{
_dataFetcher.Data.Add(contract);
}
InvokeAsync(StateHasChanged);
}

private FetchData<List<Contract>> _dataFetcher = null!;

/// <summary>
/// Removes a contract from the list.
/// </summary>
Expand Down
30 changes: 23 additions & 7 deletions src/Client/Pages/Admin/ContractTableRow.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,45 @@

<td>@Contract.SupplierName</td>

<td>
<button type="button"
class="btn btn-outline-primary m-0"
@onclick="@(async () => await ContractOpeningForEdit.InvokeAsync(Contract))">
<span class="oi oi-pencil"></span>
</button>
</td>

<!-- Button trigger modal -->
<td>
<button type="button" class="btn btn-danger m-0" data-bs-toggle="modal" data-bs-target="#@($"modal_{Contract.Id}")">
<button type="button"
class="btn btn-danger m-0"
data-bs-toggle="modal"
data-bs-target="#@($"modal_{Contract.Id}")">
<span class="oi oi-trash"></span>
</button>
</td>

<RemoveModal
Id=@Contract.Id
Title="Ta bort kontrakt"
Message="@($"Vill du ta bort kontraktet {Contract.Name}?")"
RemovalConfirmed="@(async () => await OnContractRemoved.InvokeAsync(Contract))"
/>
<RemoveModal
Id=@Contract.Id
Title="Ta bort kontrakt"
Message="@($"Vill du ta bort kontraktet {Contract.Name}?")"
RemovalConfirmed="@(async () => await OnContractRemoved.InvokeAsync(Contract))"/>
</tr>


@code {

/// <summary>
/// Called when a contract has been removed successfully.
/// </summary>
[Parameter]
public EventCallback<Contract> OnContractRemoved { get; set; } = EventCallback<Contract>.Empty;

/// <summary>
/// Called when a <see cref="Contract"/> is being opened to be edited.
/// </summary>
[Parameter]
public EventCallback<Contract> ContractOpeningForEdit { get; set; } = EventCallback<Contract>.Empty;

/// <summary>
/// The contract.
Expand Down
22 changes: 13 additions & 9 deletions src/Client/wwwroot/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ p {
line-height: 1.8125rem;
}

h1, h2, h3, h4, h5, h6 {
scroll-margin-top: 4.5rem;
}

h1, h2 {
font-family: 'Merriweather', Helvetica, Arial, sans-serif;
letter-spacing: 0;
Expand Down Expand Up @@ -103,22 +107,22 @@ a, .btn-link {
z-index: 1000;
}

#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}

.blazor-error-boundary::after {
content: "An error has occurred."
}
.blazor-error-boundary::after {
content: "An error has occurred."
}


table {
Expand Down
4 changes: 4 additions & 0 deletions src/Client/wwwroot/js/interop.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ function registerActivityCallback(dotNetHelper) {
dotNetHelper.invokeMethodAsync("ResetTimer");
}
}

function scrollToElement(selector) {
document.querySelector(selector).scrollIntoView({behavior: 'smooth', block: 'start'});
}
12 changes: 6 additions & 6 deletions src/Server/Controllers/ContractsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,22 @@ public IActionResult AddRecent(Contract contract)
/// <summary>
/// Creates a new contract.
/// </summary>
/// <param name="contract">The contract to add.</param>
/// <param name="contract">The contract to put.</param>
/// <param name="id">The identifier of the contract to put.</param>
/// <returns>The identifier of the stored image.</returns>
/// <response code="400">The ID of the contract was already taken.</response>
[HttpPost]
[HttpPut("{id:guid}")]
[Authorize("AdminOnly")]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult CreateContract(Contract contract)
public IActionResult CreateContract([FromBody] Contract contract, Guid id)
{
try
{
_contracts.Add(contract);
}
catch (IdentifierAlreadyTakenException e)
catch (IdentifierAlreadyTakenException)
{
Logger.LogInformation("ID of contract was already taken: {Error}", e.Message);
return BadRequest();
_contracts.UpdateContract(contract);
}

return Ok();
Expand Down
Loading

0 comments on commit 3e63173

Please sign in to comment.