Add document person editing in admin view.

This commit is contained in:
2020-05-27 19:21:09 +01:00
parent 061fcdab23
commit 0e69325c0b
9 changed files with 470 additions and 90 deletions

View File

@@ -1,64 +0,0 @@
@model Marechai.Database.Models.DocumentPerson
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Document person</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Name" class="control-label">
</label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Surname" class="control-label">
</label>
<input asp-for="Surname" class="form-control" />
<span asp-validation-for="Surname" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Alias" class="control-label">
</label>
<input asp-for="Alias" class="form-control" />
<span asp-validation-for="Alias" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="DisplayName" class="control-label">
</label>
<input asp-for="DisplayName" class="form-control" />
<span asp-validation-for="DisplayName" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Person" class="control-label">
</label>
<select asp-for="PersonId" class="form-control" asp-items="ViewBag.PersonId">
<option selected value="">
None or unknown
</option>
</select>
</div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<input class="btn btn-primary" type="submit" value="Save" />
<a asp-action="Index" class="btn btn-secondary">
Back to List
</a>
</div>
</form>
</div>
</div>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

View File

@@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<Version>3.0.99.1230</Version> <Version>3.0.99.1231</Version>
<Company>Canary Islands Computer Museum</Company> <Company>Canary Islands Computer Museum</Company>
<Copyright>Copyright © 2003-2020 Natalia Portillo</Copyright> <Copyright>Copyright © 2003-2020 Natalia Portillo</Copyright>
<Product>Canary Islands Computer Museum Website</Product> <Product>Canary Islands Computer Museum Website</Product>

View File

@@ -31,9 +31,11 @@
} }
@page "/admin/document_people/details/{Id:int}" @page "/admin/document_people/details/{Id:int}"
@page "/admin/document_people/edit/{Id:int}"
@inherits OwningComponentBase<DocumentPeopleService> @inherits OwningComponentBase<DocumentPeopleService>
@inject IStringLocalizer<DocumentPeopleService> L @inject IStringLocalizer<DocumentPeopleService> L
@inject PeopleService PeopleService @inject PeopleService PeopleService
@inject NavigationManager NavigationManager
@attribute [Authorize(Roles = "UberAdmin, Admin")] @attribute [Authorize(Roles = "UberAdmin, Admin")]
<h3>@L["Document person details"]</h3> <h3>@L["Document person details"]</h3>
<hr /> <hr />
@@ -46,48 +48,120 @@
} }
<div> <div>
@if (_editable || _model.Name != null) @if (_editing || _model.Name != null)
{ {
<Field> <Field>
<FieldLabel>@L["Name"]</FieldLabel> <FieldLabel>@L["Name"]</FieldLabel>
<TextEdit ReadOnly="!_editable" @bind-Text="@_model.Name" /> @if (_editing)
{
<Check TValue="bool" @bind-Checked="@_unknownName">@L["Unknown (name)"]</Check>
}
@if (!_editing ||
!_unknownName)
{
<Validation Validator="@ValidateName">
<TextEdit ReadOnly="!_editing" @bind-Text="@_model.Name">
<Feedback>
<ValidationError>@L["Please enter a valid name."]</ValidationError>
</Feedback>
</TextEdit>
</Validation>
}
</Field> </Field>
} }
@if (_editable || _model.Surname != null) @if (_editing || _model.Surname != null)
{ {
<Field> <Field>
<FieldLabel>@L["Surname"]</FieldLabel> <FieldLabel>@L["Surname"]</FieldLabel>
<TextEdit ReadOnly="!_editable" @bind-Text="@_model.Surname" /> @if (_editing)
{
<Check TValue="bool" @bind-Checked="@_unknownSurname">@L["Unknown (surname)"]</Check>
}
@if (!_editing ||
!_unknownSurname)
{
<Validation Validator="@ValidateSurname">
<TextEdit ReadOnly="!_editing" @bind-Text="@_model.Surname">
<Feedback>
<ValidationError>@L["Please enter a valid surname."]</ValidationError>
</Feedback>
</TextEdit>
</Validation>
}
</Field> </Field>
} }
@if (_editable || _model.Alias != null) @if (_editing || _model.Alias != null)
{ {
<Field> <Field>
<FieldLabel>@L["Alias"]</FieldLabel> <FieldLabel>@L["Alias"]</FieldLabel>
<TextEdit ReadOnly="!_editable" @bind-Text="@_model.Alias" /> @if (_editing)
{
<Check TValue="bool" @bind-Checked="@_unknownAlias">@L["Unknown (alias)"]</Check>
}
@if (!_editing ||
!_unknownAlias)
{
<Validation Validator="@ValidateAlias">
<TextEdit ReadOnly="!_editing" @bind-Text="@_model.Alias">
<Feedback>
<ValidationError>@L["Please enter a valid alias."]</ValidationError>
</Feedback>
</TextEdit>
</Validation>
}
</Field> </Field>
} }
@if (_editable || _model.DisplayName != null) @if (_editing || _model.DisplayName != null)
{ {
<Field> <Field>
<FieldLabel>@L["Display name"]</FieldLabel> <FieldLabel>@L["Display name"]</FieldLabel>
<TextEdit ReadOnly="!_editable" @bind-Text="@_model.DisplayName" /> @if (_editing)
{
<Check TValue="bool" @bind-Checked="@_unknownDisplayName">@L["Unknown (display name)"]</Check>
}
@if (!_editing ||
!_unknownDisplayName)
{
<Validation Validator="@ValidateDisplayName">
<TextEdit ReadOnly="!_editing" @bind-Text="@_model.DisplayName">
<Feedback>
<ValidationError>@L["Please enter a valid display name."]</ValidationError>
</Feedback>
</TextEdit>
</Validation>
}
</Field> </Field>
} }
@if (_editable || _model.PersonId != null) @if (_editing || _model.PersonId != null)
{ {
<Field> <Field>
<FieldLabel>@L["Linked person"]</FieldLabel> <FieldLabel>@L["Linked person"]</FieldLabel>
<Select Disabled="!_editable" TValue="int?" @bind-SelectedValue="@_model.PersonId"> @if (_editing)
@foreach (var person in _people) {
{ <Check TValue="bool" @bind-Checked="@_noLinkedPerson">@L["None (linked person)"]</Check>
<SelectItem TValue="int?" Value="@person.Id">@person.DisplayName</SelectItem> }
} @if (!_editing ||
</Select> !_noLinkedPerson)
{
<Select Disabled="!_editing" TValue="int?" @bind-SelectedValue="@_model.PersonId">
@foreach (var person in _people)
{
<SelectItem TValue="int?" Value="@person.Id">@person.DisplayName</SelectItem>
}
</Select>
}
</Field> </Field>
} }
</div> </div>
<div> <div>
<span class="btn btn-primary">@L["Edit"]</span> @if (!_editing)
<a href="/admin/companies" class="btn btn-secondary">@L["Back to list"]</a> {
<Button Color="Color.Primary" Clicked="@OnEditClicked">@L["Edit"]</Button>
}
else
{
<Button Color="Color.Success" Clicked="@OnSaveClicked">@L["Save"]</Button>
<Button Color="Color.Danger" Clicked="@OnCancelClicked">@L["Cancel"]</Button>
}
<a href="/admin/document_people" class="btn btn-secondary">@L["Back to list"]</a>
</div> </div>

View File

@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Blazorise;
using Marechai.Shared;
using Marechai.ViewModels; using Marechai.ViewModels;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@@ -7,10 +10,16 @@ namespace Marechai.Pages.Admin.Details
{ {
public partial class DocumentPerson public partial class DocumentPerson
{ {
bool _editable; bool _editing;
bool _loaded; bool _loaded;
Database.Models.DocumentPerson _model; DocumentPersonViewModel _model;
List<PersonViewModel> _people; bool _noLinkedPerson;
List<PersonViewModel> _people;
bool _unknownAlias;
bool _unknownDisplayName;
bool _unknownName;
bool _unknownSurname;
[Parameter] [Parameter]
public int Id { get; set; } public int Id { get; set; }
@@ -27,7 +36,145 @@ namespace Marechai.Pages.Admin.Details
_people = await PeopleService.GetAsync(); _people = await PeopleService.GetAsync();
_model = await Service.GetAsync(Id); _model = await Service.GetAsync(Id);
_editing = NavigationManager.ToBaseRelativePath(NavigationManager.Uri).ToLowerInvariant().
StartsWith("admin/document_people/edit/", StringComparison.InvariantCulture);
if(_editing)
SetCheckboxes();
StateHasChanged(); StateHasChanged();
} }
void SetCheckboxes()
{
_unknownAlias = string.IsNullOrWhiteSpace(_model.Alias);
_noLinkedPerson = !_model.PersonId.HasValue;
_unknownDisplayName = string.IsNullOrWhiteSpace(_model.DisplayName);
_unknownName = string.IsNullOrWhiteSpace(_model.Name);
_unknownSurname = string.IsNullOrWhiteSpace(_model.Surname);
}
void OnEditClicked()
{
_editing = true;
SetCheckboxes();
StateHasChanged();
}
async void OnCancelClicked()
{
_editing = false;
_model = await Service.GetAsync(Id);
SetCheckboxes();
StateHasChanged();
}
async void OnSaveClicked()
{
if(_unknownAlias)
_model.Alias = null;
else if(string.IsNullOrWhiteSpace(_model.Alias))
return;
if(_noLinkedPerson)
_model.PersonId = null;
else if(_model.PersonId < 0)
return;
if(_unknownAlias)
_model.Alias = null;
else if(string.IsNullOrWhiteSpace(_model.Alias))
return;
if(_unknownDisplayName)
_model.Alias = null;
else if(string.IsNullOrWhiteSpace(_model.Alias))
return;
if(_unknownName)
_model.Name = null;
else if(string.IsNullOrWhiteSpace(_model.Name))
return;
if(_unknownSurname)
_model.Surname = null;
else if(string.IsNullOrWhiteSpace(_model.Surname))
return;
if((_unknownName && !_unknownSurname) ||
(!_unknownName && _unknownSurname))
return;
// TODO: Show error here
if(_unknownName &&
_unknownSurname &&
_unknownAlias &&
_unknownDisplayName)
return;
_editing = false;
await Service.UpdateAsync(_model);
_model = await Service.GetAsync(Id);
SetCheckboxes();
StateHasChanged();
}
void ValidateName(ValidatorEventArgs e)
{
if(!(e.Value is string name))
{
e.Status = ValidationStatus.Error;
return;
}
if(name.Length < 1 ||
name.Length > 256)
{
e.ErrorText = L["Name must be smaller than 256 characters."];
e.Status = ValidationStatus.Error;
return;
}
if(!string.IsNullOrWhiteSpace(_model.Surname) &&
!_unknownSurname)
return;
e.ErrorText = L["Both name and surname must be known and filled, or both unknown."];
e.Status = ValidationStatus.Error;
}
void ValidateSurname(ValidatorEventArgs e)
{
if(!(e.Value is string surname))
{
e.Status = ValidationStatus.Error;
return;
}
if(surname.Length < 1 ||
surname.Length > 256)
{
e.ErrorText = L["Surname must be smaller than 256 characters."];
e.Status = ValidationStatus.Error;
return;
}
if(!string.IsNullOrWhiteSpace(_model.Surname) &&
!_unknownSurname)
return;
e.ErrorText = L["Both name and surname must be known and filled, or both unknown."];
e.Status = ValidationStatus.Error;
}
void ValidateAlias(ValidatorEventArgs e) =>
Validators.ValidateString(e, L["Alias must be smaller than 256 characters."], 256);
void ValidateDisplayName(ValidatorEventArgs e) =>
Validators.ValidateString(e, L["Display name must be smaller than 256 characters."], 256);
} }
} }

View File

@@ -72,9 +72,7 @@
</td> </td>
<td> <td>
<a class="btn btn-primary" href="/admin/document_people/details/@item.Id">@L["Details"]</a> <a class="btn btn-primary" href="/admin/document_people/details/@item.Id">@L["Details"]</a>
<span class="btn btn-secondary"> <a class="btn btn-primary" href="/admin/document_people/edit/@item.Id">@L["Edit"]</a>
@L["Edit"]
</span>
<Button Color="Color.Danger" Clicked="() => {ShowModal(item.Id);}">@L["Delete"]</Button> <Button Color="Color.Danger" Clicked="() => {ShowModal(item.Id);}">@L["Delete"]</Button>
</td> </td>
</tr> </tr>

View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- ReSharper disable MarkupTextTypo -->
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Unknown (name)" xml:space="preserve">
<value>Unknown</value>
<comment>Unknown, referring to a name</comment>
</data>
<data name="Unknown (surname)" xml:space="preserve">
<value>Unknown</value>
<comment>Unknown, referring to one or more surnames</comment>
</data>
<data name="Unknown (alias)" xml:space="preserve">
<value>Unknown</value>
<comment>Unknown, referring to an alias</comment>
</data>
<data name="Unknown (display name)" xml:space="preserve">
<value>Unknown</value>
<comment>Unknown, referring to a display name</comment>
</data>
<data name="None (linked person)" xml:space="preserve">
<value>None</value>
<comment>None, referring to a linked person</comment>
</data>
</root>

View File

@@ -182,4 +182,64 @@
<value>Alias</value> <value>Alias</value>
<comment>Alias</comment> <comment>Alias</comment>
</data> </data>
<data name="Save" xml:space="preserve">
<value>Guardar</value>
<comment>Save</comment>
</data>
<data name="Unknown (name)" xml:space="preserve">
<value>Desconocido</value>
<comment>Unknown, referring to a name</comment>
</data>
<data name="Unknown (surname)" xml:space="preserve">
<value>Desconocidos</value>
<comment>Unknown, referring to one or more surnames</comment>
</data>
<data name="Unknown (alias)" xml:space="preserve">
<value>Desconocido</value>
<comment>Unknown, referring to an alias</comment>
</data>
<data name="Unknown (display name)" xml:space="preserve">
<value>Desconocido</value>
<comment>Unknown, referring to a display name</comment>
</data>
<data name="None (linked person)" xml:space="preserve">
<value>Ninguna</value>
<comment>None, referring to a linked person</comment>
</data>
<data name="Please enter a valid name." xml:space="preserve">
<value>Por favor introduce un nombre válido.</value>
<comment>Please enter a valid name.</comment>
</data>
<data name="Please enter a valid surname." xml:space="preserve">
<value>Por favor introduce un apellido (o varios) válido.</value>
<comment>Please enter a valid surname.</comment>
</data>
<data name="Please enter a valid alias." xml:space="preserve">
<value>Por favor introduce un alias válido.</value>
<comment>Please enter a valid alias.</comment>
</data>
<data name="Please enter a valid display name." xml:space="preserve">
<value>Por favor introduce un nombre para mostrar válido.</value>
<comment>Please enter a valid display name.</comment>
</data>
<data name="Name must be smaller than 256 characters." xml:space="preserve">
<value>El nombre debe contener menos de 256 caracteres.</value>
<comment>Name must be smaller than 256 characters.</comment>
</data>
<data name="Both name and surname must be known and filled, or both unknown." xml:space="preserve">
<value>Tanto el nombre como el/los apellido(s) deben rellenarse, or ser desconocidos.</value>
<comment>Both name and surname must be known and filled, or both unknown.</comment>
</data>
<data name="Surname must be smaller than 256 characters." xml:space="preserve">
<value>El/los apellido(s) deben contener menos de 256 caracteres.</value>
<comment>Surname must be smaller than 256 characters.</comment>
</data>
<data name="Alias must be smaller than 256 characters." xml:space="preserve">
<value>El alias debe contener menos de 256 caracteres.</value>
<comment>Alias must be smaller than 256 characters.</comment>
</data>
<data name="Display name must be smaller than 256 characters." xml:space="preserve">
<value>El nombre para mostrar debe contener menos de 256 caracteres.</value>
<comment>Display name must be smaller than 256 characters.</comment>
</data>
</root> </root>

View File

@@ -24,7 +24,28 @@ namespace Marechai.Services
PersonId = d.PersonId PersonId = d.PersonId
}).ToListAsync(); }).ToListAsync();
public async Task<DocumentPerson> GetAsync(int id) => await _context.DocumentPeople.FindAsync(id); public async Task<DocumentPersonViewModel> GetAsync(int id) =>
await _context.DocumentPeople.Where(p => p.Id == id).Select(d => new DocumentPersonViewModel
{
Id = d.Id, Alias = d.Alias, Name = d.Name, Surname = d.Surname,
DisplayName = d.DisplayName, PersonId = d.PersonId
}).FirstOrDefaultAsync();
public async Task UpdateAsync(DocumentPersonViewModel viewModel)
{
DocumentPerson model = await _context.DocumentPeople.FindAsync(viewModel.Id);
if(model is null)
return;
model.Alias = viewModel.Alias;
model.Name = viewModel.Name;
model.Surname = viewModel.Surname;
model.DisplayName = viewModel.DisplayName;
model.PersonId = viewModel.PersonId;
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id) public async Task DeleteAsync(int id)
{ {

View File

@@ -7,5 +7,8 @@ namespace Marechai.ViewModels
public string Name { get; set; } public string Name { get; set; }
public string Person { get; set; } public string Person { get; set; }
public int? PersonId { get; set; } public int? PersonId { get; set; }
public string Alias { get; set; }
public string Surname { get; set; }
public string DisplayName { get; set; }
} }
} }