305 lines
10 KiB
Plaintext
305 lines
10 KiB
Plaintext
@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
|
|
|
|
@layout AdminLayout
|
|
|
|
<PageTitle>Manage Tours</PageTitle>
|
|
|
|
<h1 class="text-center m-4">Manage Tours</h1>
|
|
|
|
<div class="container">
|
|
<div class="mb-4">
|
|
<h4>Create New Tour</h4>
|
|
|
|
<EditForm Model="@newTour" OnValidSubmit="HandleSubmit">
|
|
<DataAnnotationsValidator />
|
|
<ValidationSummary />
|
|
|
|
<div class="mb-3">
|
|
<label>Select Transfer Destination</label>
|
|
<InputSelect @bind-Value="newTour.TransferDestinationId" class="form-control">
|
|
<option value="">-- Select --</option>
|
|
@foreach (var dest in TransferDestinations)
|
|
{
|
|
<option value="@dest.Id">@dest.Name (@dest.AddressString)</option>
|
|
}
|
|
</InputSelect>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label>Tour name</label>
|
|
<InputTextArea @bind-Value="newTour.Name" class="form-control" />
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label>Short Description</label>
|
|
<DxHtmlEditor @bind-Markup="@newTour.Bio"
|
|
Height="200px"
|
|
CustomizeToolbar="@OnCustomizeToolbar">
|
|
<DxHtmlEditorToolbar>
|
|
|
|
</DxHtmlEditorToolbar>
|
|
</DxHtmlEditor>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label>Description</label>
|
|
<DxHtmlEditor @bind-Markup="@newTour.Description"
|
|
Height="300px"
|
|
CustomizeToolbar="@OnCustomizeToolbar">
|
|
<DxHtmlEditorToolbar>
|
|
|
|
</DxHtmlEditorToolbar>
|
|
</DxHtmlEditor>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label>Cover Photo</label>
|
|
<InputFile OnChange="HandleFileChange" />
|
|
@if (!string.IsNullOrEmpty(UploadStatus))
|
|
{
|
|
<div class="text-muted mt-1">@UploadStatus</div>
|
|
}
|
|
</div>
|
|
|
|
<button class="btn btn-primary me-2" type="submit">
|
|
@(IsEditing ? "Update Tour" : "Save Tour")
|
|
</button>
|
|
@if (IsEditing)
|
|
{
|
|
<button class="btn btn-secondary" type="button" @onclick="CancelEdit">Cancel</button>
|
|
}
|
|
</EditForm>
|
|
</div>
|
|
|
|
<hr />
|
|
|
|
<div>
|
|
<h4>Existing Tours</h4>
|
|
@if (Tours.Any())
|
|
{
|
|
<div class="row">
|
|
@foreach (var tour in Tours)
|
|
{
|
|
var dest = TransferDestinations.FirstOrDefault(d => d.Id == tour.TransferDestinationId);
|
|
<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>
|
|
}
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<p>No tours available.</p>
|
|
}
|
|
</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 = [];
|
|
|
|
private TourFormModel newTour = new();
|
|
private IBrowserFile? uploadedFile;
|
|
private string UploadStatus = string.Empty;
|
|
|
|
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()
|
|
{
|
|
TransferDestinations = (await TransferDataService.GetDestinationsAsync()).ToList();
|
|
Tours = (await TourService.GetAllAsync()).ToList();
|
|
}
|
|
|
|
private void EditTour(TourInfo tour)
|
|
{
|
|
newTour = new TourFormModel
|
|
{
|
|
TransferDestinationId = tour.TransferDestinationId,
|
|
Name = tour.Title,
|
|
Bio = tour.Bio ?? string.Empty,
|
|
Description = tour.FancyDescription ?? string.Empty,
|
|
ImageBytes = null,
|
|
ImageFileName = tour.CoverImageUrl
|
|
};
|
|
|
|
IsEditing = true;
|
|
EditingTourId = tour.Id;
|
|
UploadStatus = string.Empty;
|
|
}
|
|
|
|
private void CancelEdit()
|
|
{
|
|
newTour = new TourFormModel();
|
|
uploadedFile = null;
|
|
IsEditing = false;
|
|
EditingTourId = null;
|
|
UploadStatus = string.Empty;
|
|
}
|
|
|
|
private async Task HandleSubmit()
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (IsEditing && EditingTourId.HasValue)
|
|
{
|
|
var updatedTour = new TourInfo
|
|
{
|
|
Id = EditingTourId.Value,
|
|
TransferDestinationId = newTour.TransferDestinationId,
|
|
Title = newTour.Name,
|
|
Bio = newTour.Bio,
|
|
FancyDescription = newTour.Description,
|
|
CoverImageUrl = newTour.ImageFileName // assume your service handles image URL logic
|
|
};
|
|
|
|
await TourService.UpdateAsync(updatedTour, uploadedFile);
|
|
}
|
|
else
|
|
{
|
|
var tourToCreate = new TourInfo
|
|
{
|
|
TransferDestinationId = newTour.TransferDestinationId,
|
|
Title = newTour.Name,
|
|
Bio = newTour.Bio,
|
|
FancyDescription = newTour.Description,
|
|
CoverImageUrl = newTour.ImageFileName
|
|
};
|
|
|
|
await TourService.CreateAsync(tourToCreate, uploadedFile);
|
|
}
|
|
|
|
// Reset form
|
|
newTour = new TourFormModel();
|
|
uploadedFile = null;
|
|
IsEditing = false;
|
|
EditingTourId = null;
|
|
UploadStatus = string.Empty;
|
|
|
|
Tours = (await TourService.GetAllAsync()).ToList();
|
|
}
|
|
|
|
private void HandleFileChange(InputFileChangeEventArgs e)
|
|
{
|
|
uploadedFile = e.File;
|
|
UploadStatus = $"Selected: {uploadedFile.Name}";
|
|
}
|
|
|
|
private async Task DeleteTourAsync(Guid id)
|
|
{
|
|
await TourService.DeleteAsync(id);
|
|
Tours = (await TourService.GetAllAsync()).ToList();
|
|
}
|
|
|
|
public class TourFormModel
|
|
{
|
|
[Required]
|
|
public Guid TransferDestinationId { get; set; }
|
|
|
|
[Required]
|
|
[StringLength(100, ErrorMessage = "Tour name cannot exceed 100 characters.")]
|
|
public string Name { get; set; } = string.Empty;
|
|
|
|
public string Bio { get; set; } = string.Empty;
|
|
public string Description { get; set; } = string.Empty;
|
|
|
|
public byte[]? ImageBytes { get; set; }
|
|
public string? ImageFileName { get; set; }
|
|
}
|
|
} |