feat(web): add portfolio management UI

This commit is contained in:
2026-06-02 15:21:51 +03:00
parent 242ff99a83
commit e970e94e00
6 changed files with 1026 additions and 1 deletions
@@ -1,10 +1,12 @@
@page "/group/{GroupId:guid}"
@using GmRelay.Web.Services
@using GmRelay.Shared.Domain
@using GmRelay.Web.Services.Portfolio
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@attribute [Authorize]
@inject AuthorizedSessionService SessionService
@inject AuthorizedPortfolioService PortfolioService
@inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager Navigation
@@ -138,6 +140,60 @@
</div>
}
@if (portfolioGames is not null)
{
<div class="glass-card animate-slide-up" style="margin-bottom: 1rem;">
<div class="batch-bulk-header">
<div>
<h3>Проведённые приключения</h3>
<p>Черновики и опубликованные приключения для каталога мастера.</p>
</div>
<button type="button" class="btn-gm btn-gm-success" disabled="@isCreatingDraft" @onclick="CreateDraft">
@(isCreatingDraft ? "⏳ Создаём..." : " Создать")
</button>
</div>
@if (portfolioGames.Count == 0)
{
<div class="empty-state empty-state-compact">
<div class="empty-state-title">Приключений пока нет</div>
<p class="empty-state-text">Создайте первый черновик и добавьте проведённые сессии.</p>
</div>
}
else
{
<div class="portfolio-management-list">
@foreach (var game in portfolioGames)
{
<div class="portfolio-management-row">
<div class="portfolio-management-info">
<a href="/portfolio/manage/@game.Id" class="portfolio-management-title">@game.Title</a>
<span class="status-badge @(game.IsPublic ? "status-success" : "status-neutral")">
@(game.IsPublic ? "Опубликовано" : "Черновик")
</span>
</div>
<div class="portfolio-management-meta">
<span class="status-badge status-info">@game.SessionCount игр</span>
<span class="status-badge status-info">@game.MasterCount мастеров</span>
@if (game.PendingReviewCount > 0)
{
<span class="status-badge status-warning">@game.PendingReviewCount на модерации</span>
}
</div>
<div class="portfolio-management-actions">
<a href="/portfolio/manage/@game.Id" class="btn-gm btn-gm-outline">✏️ Изменить</a>
</div>
</div>
}
</div>
}
<div style="margin-top: 0.75rem;">
<a href="/group/@GroupId/completed" class="btn-gm btn-gm-outline">📜 Все проведённые сессии</a>
</div>
</div>
}
@if (campaignTemplates is not null)
{
<div class="glass-card campaign-template-panel animate-slide-up">
@@ -481,6 +537,7 @@
private List<WebCampaignTemplate>? campaignTemplates;
private WebGroupManagement? groupManagement;
private WebPublicGroupSettings? publicSettings;
private IReadOnlyList<PortfolioGameSummary>? portfolioGames;
private List<BatchBulkEditModel> batchModels = [];
private List<CampaignTemplateUsageModel> campaignTemplateModels = [];
private Guid? promotingSessionId;
@@ -490,6 +547,7 @@
private Guid? publishingSessionId;
private string? removingCoGmId;
private bool isAddingCoGm;
private bool isCreatingDraft;
private bool savingPublicSettings;
private string? currentPlatform;
private string? externalUserId;
@@ -545,11 +603,38 @@
return;
}
portfolioGames = await PortfolioService.GetPortfolioGamesForCurrentUserAsync(GroupId);
RebuildBatchModels();
RebuildCampaignTemplateModels();
RebuildPublicSettingsModel();
}
private async Task CreateDraft()
{
errorMessage = null;
successMessage = null;
isCreatingDraft = true;
try
{
var portfolioId = await PortfolioService.CreateDraftForCurrentUserAsync(GroupId, null);
Navigation.NavigateTo($"/portfolio/manage/{portfolioId}");
}
catch (SessionAccessDeniedException)
{
Navigation.NavigateTo("/access-denied");
}
catch (Exception ex)
{
errorMessage = "Не удалось создать черновик: " + ex.Message;
}
finally
{
isCreatingDraft = false;
}
}
private async Task SavePublicSettings()
{
errorMessage = null;