Merge pull request 'feat: added Currently Watching to PrivateList' (#40) from AddCurrentlyWatchingToPrivateList into main
All checks were successful
Build Docker Linux ARM64 / build-docker-linux-arm64 (push) Successful in 4m34s

Reviewed-on: #40
This commit was merged in pull request #40.
This commit is contained in:
2026-01-04 20:30:57 +01:00

View File

@@ -16,139 +16,41 @@
<PageTitle>Private List</PageTitle> <PageTitle>Private List</PageTitle>
<div class="container-fluid mt-4 px-4"> <div class="container-fluid mt-4 px-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Private List</h2>
</div>
<!-- Grid-Layout --> <!-- Active Watching Section -->
<div class="row g-4"> @if (ActiveWatchingEntities.Any())
@foreach (var Entity in PrivateEntities) {
{ <div class="mb-5">
<div class="col-12 col-lg-6 col-xxl-4"> <h2 class="mb-4">Aktiv am Schauen</h2>
<div class="row g-4">
<div class="card shadow-sm border-0 overflow-hidden private-entity-card"> @foreach (var entity in ActiveWatchingEntities)
<div class="row g-0 h-100"> {
@RenderEntityCard(entity)
<!-- Feste Breite für das Bild, kein Umbruch --> }
<div class="col-auto" style="flex: 0 0 auto; min-width: 120px; max-width: 150px;">
@if (!string.IsNullOrEmpty(Entity.GlobalEntity?.PicturePath))
{
<img src="/@Entity.GlobalEntity.PicturePath"
class="entity-img w-100 h-100"
style="object-fit: cover;"
alt="@Entity.GlobalEntity.Title">
}
else
{
<div class="d-flex align-items-center justify-content-center h-100 bg-light text-muted small">
Kein Bild
</div>
}
</div>
<!-- Der Rest nimmt den verbleibenden Platz -->
<div class="col" style="min-width: 0;">
<div class="card-body d-flex flex-column h-100 py-2 px-3">
<!-- Header mit besserer Text-Behandlung -->
<div class="d-flex justify-content-between align-items-start mb-2">
<div style="min-width: 0; flex: 1;">
<h5 class="card-title fw-bold mb-0" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
@Entity.GlobalEntity?.Title
</h5>
<small class="text-muted meta-text">
@Entity.CreationTime.ToShortDateString()
</small>
</div>
<div class="dropdown ms-2" style="flex-shrink: 0;">
<button class="btn btn-link menu-btn" type="button" data-bs-toggle="modal" data-bs-target="#modal-@Entity.Id">
&#8942;
</button>
</div>
</div>
<select class="form-select"
value="@Entity.UserWatchStatusId"
@onchange="@(e => UpdateWatchStatus(Entity, e.Value))">
@foreach (var status in userWatchStatuses)
{
<option value="@status.Id">@status.Name</option>
}
</select>
@if (Entity.GlobalEntity?.MediaType.Name == "Series" || Entity.GlobalEntity?.MediaType.Name == "Anime")
{
<select class="form-select" value="@Entity.Season" @onchange="@(e => UpdateSeason(Entity, e.Value))">
@{
int currentSeason = Entity.Season ?? 1;
int startOffsetSeason = currentSeason > 1 ? -1 : 0;
}
@for (int i = startOffsetSeason; i <= 5; i++)
{
int season = currentSeason + i;
<option value="@season">
Staffel @season
</option>
}
</select>
<select class="form-select" value="@Entity.Episode" @onchange="@(e => UpdateEpisode(Entity, e.Value))">
@{
int currentEpisode = Entity.Episode ?? 1;
int startOffsetEpisode = currentEpisode > 1 ? -1 : 0;
}
@for (int i = startOffsetEpisode; i <= 5; i++)
{
int episode = currentEpisode + i;
<option value="@episode">
Episode @episode
</option>
}
</select>
}
<!-- Beschreibung -->
<!--<p class="card-text text-truncate-multiline flex-grow-1 text-muted small mt-1 mb-2">
@(!string.IsNullOrWhiteSpace(Entity.Description)
? Entity.Description
: (Entity.Description ?? "Keine Beschreibung."))
</p>
-->
<!-- Footer Button -->
<div class="mt-auto d-flex justify-content-end">
<button class="btn btn-outline-secondary btn-sm btn-details" type="button">Details</button>
</div>
</div>
</div>
</div>
</div>
<!-- Modal (unverändert) -->
<div class="modal fade" id="modal-@Entity.Id" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Optionen</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">Optionen...</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
</div>
</div>
</div>
</div>
</div> </div>
} </div>
</div> }
<!-- Library / Completed Section -->
@if (LibraryEntities.Any())
{
<div class="mb-5">
<h2 class="mb-4">Bibliothek</h2>
<div class="row g-4">
@foreach (var entity in LibraryEntities)
{
@RenderEntityCard(entity)
}
</div>
</div>
}
@if (!PrivateEntities.Any())
{
<div class="text-center text-muted py-5">
<p>Keine Einträge vorhanden.</p>
</div>
}
</div> </div>
@code @code
@@ -157,6 +59,9 @@
private List<UserWatchStatus> userWatchStatuses = new List<UserWatchStatus>(); private List<UserWatchStatus> userWatchStatuses = new List<UserWatchStatus>();
ApplicationUser? AppUser = new(); ApplicationUser? AppUser = new();
private List<PrivateEntity> ActiveWatchingEntities => PrivateEntities.Where(x => x.UserWatchStatus?.Name == "Watching").ToList();
private List<PrivateEntity> LibraryEntities => PrivateEntities.Where(x => x.UserWatchStatus?.Name != "Watching").ToList();
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
var AuthState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); var AuthState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
@@ -176,30 +81,130 @@
} }
} }
private RenderFragment RenderEntityCard(PrivateEntity Entity) => __builder =>
{
<div class="col-12 col-lg-6 col-xxl-4">
<div class="card shadow-sm border-0 overflow-hidden private-entity-card">
<div class="row g-0 h-100">
<div class="col-auto" style="flex: 0 0 auto; min-width: 120px; max-width: 150px;">
@if (!string.IsNullOrEmpty(Entity.GlobalEntity?.PicturePath))
{
<img src="/@Entity.GlobalEntity.PicturePath"
class="entity-img w-100 h-100"
style="object-fit: cover;"
alt="@Entity.GlobalEntity.Title">
}
else
{
<div class="d-flex align-items-center justify-content-center h-100 bg-light text-muted small">
Kein Bild
</div>
}
</div>
<!-- Content -->
<div class="col" style="min-width: 0;">
<div class="card-body d-flex flex-column h-100 py-2 px-3">
<!-- Header -->
<div class="d-flex justify-content-between align-items-start mb-2">
<div style="min-width: 0; flex: 1;">
<h5 class="card-title fw-bold mb-0" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
@Entity.GlobalEntity?.Title
</h5>
<small class="text-muted meta-text">
@Entity.CreationTime.ToShortDateString()
</small>
</div>
<div class="dropdown ms-2" style="flex-shrink: 0;">
<button class="btn btn-link menu-btn" type="button" data-bs-toggle="modal" data-bs-target="#modal-@Entity.Id">
&#8942;
</button>
</div>
</div>
<!-- Watch Status Dropdown -->
<select class="form-select"
value="@Entity.UserWatchStatusId"
@onchange="@(e => UpdateWatchStatus(Entity, e.Value))">
@foreach (var status in userWatchStatuses)
{
<option value="@status.Id">@status.Name</option>
}
</select>
@if (Entity.GlobalEntity?.MediaType.Name == "Series" || Entity.GlobalEntity?.MediaType.Name == "Anime")
{
<select class="form-select" value="@Entity.Season" @onchange="@(e => UpdateSeason(Entity, e.Value))">
@{
int currentSeason = Entity.Season ?? 1;
int startOffsetSeason = currentSeason > 1 ? -1 : 0;
}
@for (int i = startOffsetSeason; i <= 5; i++)
{
int season = currentSeason + i;
<option value="@season">Staffel @season</option>
}
</select>
<select class="form-select" value="@Entity.Episode" @onchange="@(e => UpdateEpisode(Entity, e.Value))">
@{
int currentEpisode = Entity.Episode ?? 1;
int startOffsetEpisode = currentEpisode > 1 ? -1 : 0;
}
@for (int i = startOffsetEpisode; i <= 5; i++)
{
int episode = currentEpisode + i;
<option value="@episode">Episode @episode</option>
}
</select>
}
<!-- Footer Button -->
<div class="mt-auto d-flex justify-content-end">
<button class="btn btn-outline-secondary btn-sm btn-details" type="button">Details</button>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="modal-@Entity.Id" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Optionen</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">Optionen...</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
</div>
</div>
</div>
</div>
</div>
};
private async Task UpdateWatchStatus(PrivateEntity entity, object? newValue) private async Task UpdateWatchStatus(PrivateEntity entity, object? newValue)
{ {
// Wir holen uns die gewählte ID aus dem Dropdown
if (int.TryParse(newValue?.ToString(), out int newId)) if (int.TryParse(newValue?.ToString(), out int newId))
{ {
// 1. ID im Objekt aktualisieren
entity.UserWatchStatusId = newId; entity.UserWatchStatusId = newId;
// 2. Navigation Property aktualisieren (damit die UI nicht flackert)
// Wir suchen das passende Objekt aus der geladenen Liste
entity.UserWatchStatus = userWatchStatuses.FirstOrDefault(s => s.Id == newId); entity.UserWatchStatus = userWatchStatuses.FirstOrDefault(s => s.Id == newId);
// 3. Ab in die Datenbank
await CouchLogDB.SaveChangesAsync(); await CouchLogDB.SaveChangesAsync();
} }
} }
private async Task UpdateSeason(PrivateEntity entity, object? newSeasonValue) private async Task UpdateSeason(PrivateEntity entity, object? newSeasonValue)
{ {
if(int.TryParse(newSeasonValue?.ToString(), out int newSeason)) if (int.TryParse(newSeasonValue?.ToString(), out int newSeason))
{ {
entity.Season = newSeason; entity.Season = newSeason;
entity.Episode = 1; entity.Episode = 1;
await CouchLogDB.SaveChangesAsync(); await CouchLogDB.SaveChangesAsync();
} }
} }
@@ -209,7 +214,6 @@
if (int.TryParse(newEpisodeValue?.ToString(), out int newEpisode)) if (int.TryParse(newEpisodeValue?.ToString(), out int newEpisode))
{ {
entity.Episode = newEpisode; entity.Episode = newEpisode;
await CouchLogDB.SaveChangesAsync(); await CouchLogDB.SaveChangesAsync();
} }
} }