- Bild-Skalierung in Kartenansicht gefixt (object-position: top, 300px) - Admin-Slider für Zeichenlimit (4–32.000) als Modul-Setting - Textarea durch RichTextEditor (Quill.js) ersetzt - PDF: HTML-Parsing, Einzelperson-Filter, Autorisierung für alle User
262 lines
10 KiB
Plaintext
262 lines
10 KiB
Plaintext
@using SZUAbsolventenverein.Module.HallOfFame.Services
|
|
@using SZUAbsolventenverein.Module.HallOfFame.Models
|
|
@using Oqtane.Security
|
|
@using Oqtane.Shared
|
|
|
|
@namespace SZUAbsolventenverein.Module.HallOfFame
|
|
@inherits ModuleBase
|
|
@inject IHallOfFameService HallOfFameService
|
|
@inject NavigationManager NavigationManager
|
|
@inject IStringLocalizer<Index> Localizer
|
|
@inject ISettingService SettingService
|
|
|
|
@if (_HallOfFames == null)
|
|
{
|
|
<p><em>Loading...</em></p>
|
|
}
|
|
else
|
|
{
|
|
<div class="row mb-4 align-items-center">
|
|
<div class="col-md-6">
|
|
<div class="input-group">
|
|
<span class="input-group-text"><i class="oi oi-magnifying-glass"></i></span>
|
|
<input type="text" class="form-control" placeholder="Suchen nach Namen oder Begriffen..."
|
|
@bind="_searchText" @bind:event="oninput" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="input-group">
|
|
<select class="form-select" @bind="_sortOption">
|
|
<option value="CreatedOn">Datum</option>
|
|
<option value="Name">Name</option>
|
|
<option value="Year">Jahrgang</option>
|
|
</select>
|
|
<button class="btn btn-outline-secondary" type="button" @onclick="ToggleSortDirection"
|
|
title="@(_sortAscending ? "Aufsteigend" : "Absteigend")">
|
|
<i class="oi @(_sortAscending ? "oi-arrow-top" : "oi-arrow-bottom")"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 text-end">
|
|
@if (PageState.User != null)
|
|
{
|
|
if (_myEntry != null)
|
|
{
|
|
<ActionLink Action="Edit" Parameters="@($"id=" + _myEntry.HallOfFameId.ToString())" Text="Mein Eintrag" />
|
|
}
|
|
else
|
|
{
|
|
<ActionLink Action="Add" Text="Eintragen" />
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin + ";" + RoleNames.Host))
|
|
{
|
|
<div class="row mb-3 align-items-center">
|
|
<div class="col-md-8">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<label class="form-label mb-0 text-nowrap" style="font-size: 0.85rem;"><i
|
|
class="oi oi-pencil me-1"></i>Zeichenlimit:</label>
|
|
<input type="range" class="form-range flex-grow-1" min="4" max="32000" step="1" value="@_charLimit"
|
|
@oninput="OnCharLimitChanged" style="min-width: 200px;" />
|
|
<input type="number" class="form-control form-control-sm" style="width: 90px;" min="4" max="32000"
|
|
value="@_charLimit" @onchange="OnCharLimitInputChanged" />
|
|
<button class="btn btn-sm btn-outline-primary" @onclick="SaveCharLimit" title="Zeichenlimit speichern">
|
|
<i class="oi oi-check"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 text-muted" style="font-size: 0.8rem;">
|
|
Maximale Zeichen für Beschreibungen
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@if (@_HallOfFames.Count != 0)
|
|
{
|
|
<div class="row">
|
|
@foreach (var item in FilteredHallOfFames)
|
|
{
|
|
<div class="col-md-4 mb-3">
|
|
<div class="card h-100">
|
|
@if (!string.IsNullOrEmpty(item.Image))
|
|
{
|
|
<img src="@item.Image" class="card-img-top" alt="@item.Name"
|
|
style="height: 300px; object-fit: cover; object-position: top;">
|
|
}
|
|
<div class="card-body d-flex flex-column">
|
|
<h5 class="card-title">@item.Name (@item.Year)</h5>
|
|
@if (item.IsReported && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin + ";" +
|
|
RoleNames.Host))
|
|
{
|
|
<div class="alert alert-danger p-1 mb-2" style="font-size: 0.8rem;">
|
|
<i class="oi oi-warning me-1"></i> <strong>Dieser Eintrag wurde gemeldet!</strong>
|
|
</div>
|
|
}
|
|
<div class="hof-description-container" style="
|
|
max-height: 150px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
flex-grow: 1;
|
|
margin-bottom: 0.5rem;
|
|
">
|
|
@((MarkupString)(item.Description ?? ""))
|
|
</div>
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<ActionLink Action="Details" Parameters="@($"id=" + item.HallOfFameId.ToString())"
|
|
Class="btn btn-sm btn-outline-primary" Text="Details ansehen" />
|
|
<div>
|
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin + ";" + RoleNames.Host))
|
|
{
|
|
<button class="btn btn-sm btn-outline-danger me-1" @onclick="@(() => Delete(item.HallOfFameId))"
|
|
title="Löschen"><i class="oi oi-trash"></i></button>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="alert alert-info">
|
|
Es sind noch keine Hall-of-Fame-Einträge veröffentlicht.
|
|
</div>
|
|
}
|
|
}
|
|
|
|
@code {
|
|
|
|
public override List<Resource> Resources => new List<Resource>()
|
|
{
|
|
new Stylesheet("_content/SZUAbsolventenverein.Module.HallOfFame/Module.css"),
|
|
new Script("_content/SZUAbsolventenverein.Module.HallOfFame/Module.js")
|
|
};
|
|
|
|
List<HallOfFame> _HallOfFames;
|
|
HallOfFame _myEntry;
|
|
string _searchText = "";
|
|
string _sortOption = "CreatedOn";
|
|
bool _sortAscending = false;
|
|
int _charLimit = 500;
|
|
|
|
IEnumerable<HallOfFame> FilteredHallOfFames
|
|
{
|
|
get
|
|
{
|
|
var items = _HallOfFames.AsEnumerable();
|
|
if (!string.IsNullOrEmpty(_searchText))
|
|
{
|
|
items = items.Where(i =>
|
|
(i.Name?.Contains(_searchText, StringComparison.OrdinalIgnoreCase) ?? false) ||
|
|
(i.Description?.Contains(_searchText, StringComparison.OrdinalIgnoreCase) ?? false)
|
|
);
|
|
}
|
|
|
|
items = _sortOption switch
|
|
{
|
|
"Name" => _sortAscending ? items.OrderBy(i => i.Name) : items.OrderByDescending(i => i.Name),
|
|
"Year" => _sortAscending ? items.OrderBy(i => i.Year) : items.OrderByDescending(i => i.Year),
|
|
"CreatedOn" or _ => _sortAscending ? items.OrderBy(i => i.CreatedOn) : items.OrderByDescending(i => i.CreatedOn)
|
|
};
|
|
|
|
return items;
|
|
}
|
|
}
|
|
|
|
private string TruncateDescription(string description)
|
|
{
|
|
if (string.IsNullOrEmpty(description)) return "";
|
|
const int maxLength = 150;
|
|
if (description.Length <= maxLength) return description;
|
|
return description.Substring(0, maxLength).TrimEnd() + "...";
|
|
}
|
|
|
|
private void ToggleSortDirection()
|
|
{
|
|
_sortAscending = !_sortAscending;
|
|
}
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadData();
|
|
}
|
|
|
|
private async Task LoadData()
|
|
{
|
|
try
|
|
{
|
|
var allEntries = await HallOfFameService.GetHallOfFamesAsync(ModuleState.ModuleId);
|
|
_HallOfFames = allEntries.Where(i => i.Status == "Published").ToList();
|
|
|
|
if (PageState.User != null)
|
|
{
|
|
_myEntry = await HallOfFameService.GetHallOfFameByUserIdAsync(PageState.User.UserId, ModuleState.ModuleId);
|
|
}
|
|
|
|
// Load character limit setting
|
|
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
|
var charLimitStr = SettingService.GetSetting(settings, "CharLimit", "500");
|
|
if (int.TryParse(charLimitStr, out var parsed) && parsed >= 4 && parsed <= 32000)
|
|
{
|
|
_charLimit = parsed;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await logger.LogError(ex, "Error Loading HallOfFame {Error}", ex.Message);
|
|
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
|
|
}
|
|
}
|
|
|
|
private void OnCharLimitChanged(ChangeEventArgs e)
|
|
{
|
|
if (int.TryParse(e.Value?.ToString(), out var val) && val >= 4 && val <= 32000)
|
|
{
|
|
_charLimit = val;
|
|
}
|
|
}
|
|
|
|
private void OnCharLimitInputChanged(ChangeEventArgs e)
|
|
{
|
|
if (int.TryParse(e.Value?.ToString(), out var val))
|
|
{
|
|
_charLimit = Math.Clamp(val, 4, 32000);
|
|
}
|
|
}
|
|
|
|
private async Task SaveCharLimit()
|
|
{
|
|
try
|
|
{
|
|
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
|
SettingService.SetSetting(settings, "CharLimit", _charLimit.ToString());
|
|
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
|
AddModuleMessage($"Zeichenlimit auf {_charLimit} gesetzt.", MessageType.Success);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await logger.LogError(ex, "Error Saving CharLimit {Error}", ex.Message);
|
|
AddModuleMessage("Fehler beim Speichern des Zeichenlimits.", MessageType.Error);
|
|
}
|
|
}
|
|
|
|
private async Task Delete(int hallOfFameId)
|
|
{
|
|
try
|
|
{
|
|
await HallOfFameService.DeleteHallOfFameAsync(hallOfFameId, ModuleState.ModuleId);
|
|
AddModuleMessage("Eintrag wurde gelöscht.", MessageType.Success);
|
|
await LoadData();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await logger.LogError(ex, "Error Deleting HallOfFame {Error}", ex.Message);
|
|
AddModuleMessage("Fehler beim Löschen des Eintrags.", MessageType.Error);
|
|
}
|
|
}
|
|
} |