This commit is contained in:
Adam 2025-09-26 11:40:38 +02:00
parent d1ba700c01
commit 5fad939eb2
5 changed files with 134 additions and 60 deletions

View File

@ -10,7 +10,7 @@
<DxMaskedInput @bind-Value="@LoginModel.Email"
Id="Email"
style="width: 100%;"
Mask="@EmailMask"
MaskMode="MaskMode.RegEx">
<DxRegExMaskProperties MaskAutoCompleteMode="@((MaskAutoCompleteMode)AutoCompleteMode)"

View File

@ -1,10 +1,13 @@
@page "/sysadmin/manage-tours"
@using System.ComponentModel.DataAnnotations
@using DevExpress.Blazor.Internal
@using DevExpress.Blazor.Office
@using TIAM.Entities.Transfers
@using TIAMWebApp.Shared.Application.Interfaces
@using TIAMWebApp.Shared.Application.Models
@using TIAMWebApp.Shared.Application.Services
@using TIAMSharedUI.Shared
@using DevExpress.Blazor
@inject ITransferDataService TransferDataService
@inject TourService TourService
@inject NavigationManager Navigation
@ -40,13 +43,25 @@
</div>
<div class="mb-3">
<label>ShortDescription</label>
<InputTextArea @bind-Value="newTour.Bio" class="form-control" />
<label>Short Description</label>
<DxHtmlEditor @bind-Markup="@newTour.Bio"
Height="200px"
CustomizeToolbar="@OnCustomizeToolbar">
<DxHtmlEditorToolbar>
</DxHtmlEditorToolbar>
</DxHtmlEditor>
</div>
<div class="mb-3">
<label>Description</label>
<InputTextArea @bind-Value="newTour.Description" class="form-control" />
<DxHtmlEditor @bind-Markup="@newTour.Description"
Height="300px"
CustomizeToolbar="@OnCustomizeToolbar">
<DxHtmlEditorToolbar>
</DxHtmlEditorToolbar>
</DxHtmlEditor>
</div>
<div class="mb-3">
@ -74,23 +89,55 @@
<h4>Existing Tours</h4>
@if (Tours.Any())
{
<ul class="list-group">
<div class="row">
@foreach (var tour in Tours)
{
var dest = TransferDestinations.FirstOrDefault(d => d.Id == tour.TransferDestinationId);
<li class="list-group-item d-flex justify-content-between align-items-center">
<img src="@tour.CoverImageUrl" style="max-height: 100px;"/>
<span>
<strong>@tour?.Title</strong> — @tour.FancyDescription
</span>
<span>
@tour.Bio
</span>
<button class="btn btn-sm btn-secondary me-2" @onclick="() => EditTour(tour)">Edit</button>
<button class="btn btn-sm btn-danger" @onclick="() => DeleteTourAsync(tour.Id)">Delete</button>
</li>
<div class="col-lg-6 col-xl-4 mb-4">
<div class="card h-100">
@if (!string.IsNullOrEmpty(tour.CoverImageUrl))
{
<img src="@tour.CoverImageUrl" class="card-img-top" style="height: 200px; object-fit: cover;" alt="Tour cover image"/>
}
<div class="card-body d-flex flex-column">
<h5 class="card-title">@tour?.Title</h5>
@if (!string.IsNullOrEmpty(tour.Bio))
{
<div class="card-text mb-3">
<strong>Short Description:</strong>
<div class="html-content">
@((MarkupString)tour.Bio)
</div>
</div>
}
@if (!string.IsNullOrEmpty(tour.FancyDescription))
{
<div class="card-text mb-3">
<strong>Description:</strong>
<div class="html-content">
@((MarkupString)tour.FancyDescription)
</div>
</div>
}
@if (dest != null)
{
<div class="card-text mb-3">
<strong>Destination:</strong> @dest.Name (@dest.AddressString)
</div>
}
<div class="mt-auto">
<button class="btn btn-sm btn-secondary me-2" @onclick="() => EditTour(tour)">Edit</button>
<button class="btn btn-sm btn-danger" @onclick="() => DeleteTourAsync(tour.Id)">Delete</button>
</div>
</div>
</div>
</div>
}
</ul>
</div>
}
else
{
@ -99,6 +146,35 @@
</div>
</div>
<style>
.html-content {
max-height: 150px;
overflow-y: auto;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
padding: 0.5rem;
background-color: #f8f9fa;
}
.html-content p {
margin-bottom: 0.5rem;
}
.html-content p:last-child {
margin-bottom: 0;
}
.html-content ul, .html-content ol {
margin-bottom: 0.5rem;
padding-left: 1.5rem;
}
.html-content img {
max-width: 100%;
height: auto;
}
</style>
@code {
private List<TransferDestination> TransferDestinations = [];
private List<TourInfo> Tours = [];
@ -110,6 +186,13 @@
private bool IsEditing = false;
private Guid? EditingTourId = null;
void OnCustomizeToolbar(IToolbar toolbar)
{
// Returns the first group
IBarGroup firstGroup = toolbar.Groups[0];
// Returns the "Table" group
IBarGroup tableGroup = toolbar.Groups[HtmlEditorToolbarGroupNames.Table];
}
protected override async Task OnInitializedAsync()
{
@ -123,8 +206,8 @@
{
TransferDestinationId = tour.TransferDestinationId,
Name = tour.Title,
Bio = tour.Bio,
Description = tour.FancyDescription,
Bio = tour.Bio ?? string.Empty,
Description = tour.FancyDescription ?? string.Empty,
ImageBytes = null,
ImageFileName = tour.CoverImageUrl
};
@ -142,35 +225,7 @@
EditingTourId = null;
UploadStatus = string.Empty;
}
// private async Task SaveTourAsync()
// {
// if (uploadedFile is not null)
// {
// using var stream = uploadedFile.OpenReadStream(maxAllowedSize: 5 * 1024 * 1024);
// using var ms = new MemoryStream();
// await stream.CopyToAsync(ms);
// newTour.ImageBytes = ms.ToArray();
// newTour.ImageFileName = uploadedFile.Name;
// }
// TourInfo tourToCreate = new TourInfo();
// tourToCreate.TransferDestinationId = newTour.TransferDestinationId;
// tourToCreate.FancyDescription = newTour.Description;
// tourToCreate.Title = newTour.Name;
// tourToCreate.CoverImageUrl = "";
// if (tourToCreate.TransferDestinationId == Guid.Empty)
// {
// throw new InvalidOperationException("Transfer Destination must be selected.");
// }
// await TourService.CreateAsync(tourToCreate, uploadedFile);
// newTour = new();
// uploadedFile = null;
// UploadStatus = string.Empty;
// Tours = (await TourService.GetAllAsync()).ToList();
// }
private async Task HandleSubmit()
{
if (uploadedFile is not null)
@ -181,7 +236,6 @@
newTour.ImageBytes = ms.ToArray();
newTour.ImageFileName = uploadedFile.Name;
}
if (IsEditing && EditingTourId.HasValue)
{
@ -203,6 +257,7 @@
{
TransferDestinationId = newTour.TransferDestinationId,
Title = newTour.Name,
Bio = newTour.Bio,
FancyDescription = newTour.Description,
CoverImageUrl = newTour.ImageFileName
};
@ -241,12 +296,10 @@
[StringLength(100, ErrorMessage = "Tour name cannot exceed 100 characters.")]
public string Name { get; set; } = string.Empty;
public string? Bio { get; set; }
public string? Description { get; set; }
public string Bio { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public byte[]? ImageBytes { get; set; }
public string? ImageFileName { get; set; }
}
}
}

View File

@ -18,14 +18,14 @@
{
<div class="item" style="background-image: url(@item.CoverImageUrl); height: 600px !important">
<div class="added" style="position: absolute; top: 100px; left:0px; text-align:center; padding:10px; width:100%">
<p>@item.Bio</p>
<div class="added" style="position: absolute; top: 0px; left:0px; text-align:center; padding:10px; width:100%;">
@((MarkupString)item.Bio)
</div>
<div class="item-desc" style="height: 100%; background-color: rgba(0,0,0,0.3);">
@* <p style="padding-top:70px">@item.Created</p> *@
<div style="margin-top:70px; position: relative; z-index: 10; height:450px; overflow-y: scroll;">
<p style="">@item.FancyDescription</p>
@((MarkupString)item.FancyDescription)
</div>
@* <p>@item.TransferDestinationId</p> *@
<div>

View File

@ -171,6 +171,27 @@ select {
background: no-repeat 50% / cover;
}
/* ===== Scrollbar CSS ===== */
/* Firefox */
* {
scrollbar-width: auto;
scrollbar-color: #643172 rgba( 31, 38, 135, 0 );
}
/* Chrome, Edge, and Safari */
*::-webkit-scrollbar {
width: 16px;
}
*::-webkit-scrollbar-track {
background: rgba( 31, 38, 135, 0 );
}
*::-webkit-scrollbar-thumb {
background-color: #643172;
border-radius: 10px;
border: 0px solid #ffffff;
}
.inputwizardwrapper {
/*max-width: 450px;*/

View File

@ -2,9 +2,9 @@
"Id": "00000000-0000-0000-0000-000000000000",
"TransferDestinationId": "171fcca4-ffd5-4380-9402-f27447af1e08",
"Title": "Downtown",
"Bio": "hjkh kh kjs dhasjkldhajk dhkasjdh sajkldh asjkldh asjkldhasjldh asjkldh askldh asjkdh aklsdhajkdhkalh djksahdjkashd jkalhal",
"FancyDescription": "fdshjklhsjfkslh dfjkhsjklfhd jkkslhfdsjkfh dskfh dsklhf dsjkl fdhjkl fdshjk fkjld hlkdskl dsjk fdjlfh dskh fdsjkfdshl khf dk hldsk hldskh dsjk hdslkf dhjkh dsfk fdslsk dksajdklaj dkla\u00E9jdksal\u00E9dj kasl\u00E9dj kasl\u00E9jdka l\u00E9djkasl\u00E9j dkla\u00E9sjdkl\u00E9 ajdklas\u00E9 jdkl\u00E9asj dklsaj dklsj dka dksa jdklas daklsj dklaj dklsaj dklasj dklaj dkl\u00E9a ldjfskl\u00E9fjk lds\u00E9jfksld\u00E9 fjklsd\u00E9jkfdl slkfj ldkfj kdfj kdfjksljfdklsjfdsl kfjdskljfkdslfjkdsl jkl",
"Bio": "\u003Cp\u003E\u003Cstrong\u003Ehjkh kh kjs dhasjkldhajk dhkasjdh sajkldh asjkldh\u003C/strong\u003E \u003C/p\u003E\u003Cp\u003E\u003Cbr\u003E\u003C/p\u003E\u003Cp\u003Easjkldhasjldh asjkldh askldh asjkdh aklsdhajkdhkalh djksahdjkashd jkalhal\u003C/p\u003E",
"FancyDescription": "\u003Cp\u003E\u003Cstrong\u003Efdshjklhsjfkslh dfjkhsjklfhd jkkslhfdsjkfh dskfh \u003C/strong\u003E\u003Cem\u003Edsklhf dsjkl fdhjkl fdshjk fkjld hl\u003C/em\u003Ek\u003C/p\u003E\u003Cp\u003E\u003Cbr\u003E\u003C/p\u003E\u003Cp\u003Edskl dsjk fdjlfh dskh fdsjkfdshl khf dk hldsk hldskh dsjk hdslkf dhjkh dsfk fdslsk dksajdklaj dkla\u00E9jdksal\u00E9dj kasl\u00E9dj kasl\u00E9jdka l\u00E9djkasl\u00E9j dkla\u00E9sjdkl\u00E9 ajdklas\u00E9 jdkl\u00E9asj dklsaj dklsj dka dksa jdklas daklsj dklaj dklsaj dklasj dklaj dkl\u00E9a ldjfskl\u00E9fjk lds\u00E9jfksld\u00E9 fjklsd\u00E9jkfdl slkfj ldkfj kdfj kdfjksljfdklsjfdsl kfjdskljfkdslfjkdsl jkl\u003C/p\u003E",
"CoverImageUrl": "/uploads/tourcovers/171fcca4-ffd5-4380-9402-f27447af1e08.png",
"Created": "0001-01-01T00:00:00",
"Modified": "2025-09-24T16:28:17.0140043Z"
"Modified": "2025-09-26T00:16:17.8690382Z"
}