259 lines
13 KiB
Plaintext
259 lines
13 KiB
Plaintext
@model StoryCalendarViewModel
|
|
@using System.Globalization
|
|
@{
|
|
ViewData["Title"] = $"Calendar - {Model.Project.ProjectName}";
|
|
ViewData["ProjectSection"] = "Calendar";
|
|
var selectedMonthValue = Model.SelectedMonth?.ToString("yyyy-MM", CultureInfo.InvariantCulture);
|
|
var selectedDateValue = Model.SelectedDate?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
|
|
var showBookLabels = !Model.SelectedBookID.HasValue;
|
|
var weekdays = new[] { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
|
|
}
|
|
|
|
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
|
<a asp-controller="Projects" asp-action="Index">Projects</a>
|
|
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
|
|
<span>Calendar</span>
|
|
</nav>
|
|
|
|
<partial name="_ProjectSectionNav" model="Model.Project" />
|
|
|
|
<div class="page-heading story-calendar-heading">
|
|
<div>
|
|
<p class="eyebrow">Story time</p>
|
|
<h1>Calendar</h1>
|
|
<p class="lead-text">@Model.StoryRangeLabel</p>
|
|
</div>
|
|
<div class="story-calendar-stats" aria-label="Calendar summary">
|
|
<span><strong>@Model.SceneCount</strong> dated scenes</span>
|
|
<span><strong>@Model.ActiveStoryDayCount</strong> story days</span>
|
|
<span><strong>@Model.UnplacedScenes.Count</strong> unplaced</span>
|
|
</div>
|
|
</div>
|
|
|
|
<section class="card mb-3 story-calendar-filter">
|
|
<div class="card-body">
|
|
<form asp-action="ApplyFilters" method="post" class="row g-2 align-items-end">
|
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
|
<div class="col-md-5 col-lg-4">
|
|
<label class="form-label" for="bookId">Book</label>
|
|
<select class="form-select" id="bookId" name="bookId" asp-items="Model.BookOptions"></select>
|
|
</div>
|
|
<div class="col-md-4 col-lg-3">
|
|
<label class="form-label" for="month">Month</label>
|
|
<select class="form-select" id="month" name="month" disabled="@(!Model.MonthOptions.Any())">
|
|
@if (!Model.MonthOptions.Any())
|
|
{
|
|
<option value="">No dated scenes</option>
|
|
}
|
|
else
|
|
{
|
|
@foreach (var option in Model.MonthOptions)
|
|
{
|
|
<option value="@option.Value" selected="@option.Selected">@option.Text</option>
|
|
}
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3 col-lg-2">
|
|
<button class="btn btn-primary w-100" type="submit">Apply</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</section>
|
|
|
|
@if (!Model.HasDatedScenes)
|
|
{
|
|
<section class="card story-calendar-empty">
|
|
<div class="card-body">
|
|
<h2>No dated scenes yet</h2>
|
|
<p class="muted mb-0">Scenes will appear here once canonical scene chronology has a resolved start date.</p>
|
|
</div>
|
|
</section>
|
|
}
|
|
else
|
|
{
|
|
<section class="story-calendar-shell">
|
|
<div class="story-calendar-toolbar">
|
|
<div class="button-row">
|
|
<a class="btn btn-outline-secondary btn-sm"
|
|
asp-action="Index"
|
|
asp-route-projectId="@Model.Project.ProjectID"
|
|
asp-route-bookId="@Model.SelectedBookID"
|
|
asp-route-month="@Model.PreviousMonth?.ToString("yyyy-MM", CultureInfo.InvariantCulture)">Previous month</a>
|
|
<a class="btn btn-outline-secondary btn-sm"
|
|
asp-action="Index"
|
|
asp-route-projectId="@Model.Project.ProjectID"
|
|
asp-route-bookId="@Model.SelectedBookID"
|
|
asp-route-month="@Model.NextMonth?.ToString("yyyy-MM", CultureInfo.InvariantCulture)">Next month</a>
|
|
@if (Model.FirstStoryMonth.HasValue)
|
|
{
|
|
<a class="btn btn-outline-secondary btn-sm"
|
|
asp-action="Index"
|
|
asp-route-projectId="@Model.Project.ProjectID"
|
|
asp-route-bookId="@Model.SelectedBookID"
|
|
asp-route-month="@Model.FirstStoryMonth.Value.ToString("yyyy-MM", CultureInfo.InvariantCulture)">First dated scene</a>
|
|
}
|
|
</div>
|
|
<div class="story-calendar-story-months">
|
|
@if (Model.PreviousStoryMonth.HasValue)
|
|
{
|
|
<a asp-action="Index"
|
|
asp-route-projectId="@Model.Project.ProjectID"
|
|
asp-route-bookId="@Model.SelectedBookID"
|
|
asp-route-month="@Model.PreviousStoryMonth.Value.ToString("yyyy-MM", CultureInfo.InvariantCulture)">Previous story month</a>
|
|
}
|
|
@if (Model.NextStoryMonth.HasValue)
|
|
{
|
|
<a asp-action="Index"
|
|
asp-route-projectId="@Model.Project.ProjectID"
|
|
asp-route-bookId="@Model.SelectedBookID"
|
|
asp-route-month="@Model.NextStoryMonth.Value.ToString("yyyy-MM", CultureInfo.InvariantCulture)">Next story month</a>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="story-calendar-layout">
|
|
<div class="story-calendar-grid-wrap">
|
|
<div class="story-calendar-month" aria-label="@Model.SelectedMonthLabel">
|
|
<div class="story-calendar-month-title">@Model.SelectedMonthLabel</div>
|
|
<div class="story-calendar-weekdays">
|
|
@foreach (var weekday in weekdays)
|
|
{
|
|
<div>@weekday</div>
|
|
}
|
|
</div>
|
|
<div class="story-calendar-grid">
|
|
@foreach (var week in Model.Weeks)
|
|
{
|
|
@foreach (var day in week.Days)
|
|
{
|
|
var visibleScenes = day.Scenes.Take(3).ToList();
|
|
var hiddenCount = Math.Max(0, day.Scenes.Count - visibleScenes.Count);
|
|
<a class="story-calendar-day @(day.IsInSelectedMonth ? "" : "is-overflow") @(day.IsSelected ? "is-selected" : "")"
|
|
asp-action="Index"
|
|
asp-route-projectId="@Model.Project.ProjectID"
|
|
asp-route-bookId="@Model.SelectedBookID"
|
|
asp-route-month="@selectedMonthValue"
|
|
asp-route-date="@day.QueryValue"
|
|
aria-label="@day.DayLabel">
|
|
<span class="story-calendar-day-number">@day.DayNumber</span>
|
|
<span class="story-calendar-day-scenes">
|
|
@foreach (var scene in visibleScenes)
|
|
{
|
|
<span class="story-calendar-scene-chip">
|
|
@if (showBookLabels)
|
|
{
|
|
<span class="story-calendar-book">@scene.BookLabel</span>
|
|
}
|
|
<span>@scene.SceneLabel</span>
|
|
<strong>@scene.Title</strong>
|
|
</span>
|
|
}
|
|
@if (hiddenCount > 0)
|
|
{
|
|
<span class="story-calendar-more">+ @hiddenCount more</span>
|
|
}
|
|
</span>
|
|
</a>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<aside class="story-calendar-detail" aria-label="Selected day">
|
|
@if (Model.SelectedDay is null)
|
|
{
|
|
<h2>Select a story day</h2>
|
|
<p class="muted">Choose a date in the grid to inspect its scenes in narrative order.</p>
|
|
}
|
|
else
|
|
{
|
|
<h2>@Model.SelectedDay.DayLabel</h2>
|
|
@if (!Model.SelectedDay.Scenes.Any())
|
|
{
|
|
<p class="muted">No scenes are placed on this story date.</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="story-calendar-detail-list">
|
|
@foreach (var scene in Model.SelectedDay.Scenes)
|
|
{
|
|
<article class="story-calendar-detail-scene">
|
|
<div class="story-calendar-detail-meta">
|
|
@if (showBookLabels)
|
|
{
|
|
<span>@scene.BookTitle</span>
|
|
}
|
|
<span>Chapter @scene.ChapterNumber:g</span>
|
|
<span>Scene @scene.SceneNumber:g</span>
|
|
</div>
|
|
<h3>@scene.Title</h3>
|
|
<dl>
|
|
@if (!string.IsNullOrWhiteSpace(scene.Pov))
|
|
{
|
|
<dt>POV</dt>
|
|
<dd>@scene.Pov</dd>
|
|
}
|
|
<dt>Time</dt>
|
|
<dd>@scene.TimeLabel</dd>
|
|
</dl>
|
|
@if (!string.IsNullOrWhiteSpace(scene.Summary))
|
|
{
|
|
<p>@scene.Summary</p>
|
|
}
|
|
<div class="button-row">
|
|
<a class="btn btn-outline-primary btn-sm" asp-controller="Scenes" asp-action="Edit" asp-route-id="@scene.SceneID">Open scene</a>
|
|
<a class="btn btn-outline-secondary btn-sm"
|
|
asp-controller="Timeline"
|
|
asp-action="Index"
|
|
asp-route-projectId="@Model.Project.ProjectID"
|
|
asp-route-bookId="@scene.BookID"
|
|
asp-route-selectedSceneId="@scene.SceneID">Open in Timeline</a>
|
|
</div>
|
|
</article>
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
</aside>
|
|
</div>
|
|
</section>
|
|
}
|
|
|
|
<section class="card story-calendar-unplaced">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<strong>Unplaced scenes</strong>
|
|
<button class="btn btn-outline-secondary btn-sm" type="button" data-bs-toggle="collapse" data-bs-target="#story-calendar-unplaced" aria-expanded="false" aria-controls="story-calendar-unplaced">
|
|
@Model.UnplacedScenes.Count scenes
|
|
</button>
|
|
</div>
|
|
<div class="collapse" id="story-calendar-unplaced">
|
|
<div class="card-body">
|
|
@if (!Model.UnplacedScenes.Any())
|
|
{
|
|
<p class="muted mb-0">All scenes in this scope have resolved story dates.</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="story-calendar-unplaced-list">
|
|
@foreach (var scene in Model.UnplacedScenes)
|
|
{
|
|
<article>
|
|
<div>
|
|
<strong>@scene.Title</strong>
|
|
<span>@scene.BookTitle · Chapter @scene.ChapterNumber:g · Scene @scene.SceneNumber:g</span>
|
|
@if (!string.IsNullOrWhiteSpace(scene.RelativeTimeText))
|
|
{
|
|
<small>@scene.RelativeTimeText</small>
|
|
}
|
|
</div>
|
|
<a class="btn btn-outline-primary btn-sm" asp-controller="Scenes" asp-action="Edit" asp-route-id="@scene.SceneID">Open scene</a>
|
|
</article>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</section>
|