alternate schedule and template consistency (#2757)

* refactor classic and block schedules to use same alternate schedule selector

* handle start year and end year

* add migrations for classic and block schedules

* allow editing block template start and end year

* add tests that include years

* add date range editing to classic (alternate) schedules

* fix running tests locally

* restore media files load; needed for local folder scanners

* update changelog

* feedback
This commit is contained in:
Jason Dove
2026-01-06 12:51:07 -06:00
committed by GitHub
parent cc521326d9
commit effb96a2c2
40 changed files with 15366 additions and 736 deletions
@@ -4,6 +4,7 @@
@using ErsatzTV.Application.Channels
@using ErsatzTV.Application.Playouts
@using ErsatzTV.Application.ProgramSchedules
@using ErsatzTV.Core.Scheduling
@implements IDisposable
@inject NavigationManager NavigationManager
@inject ILogger<ScheduleItemsEditor> Logger
@@ -106,6 +107,70 @@
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Limit to Date Range</MudText>
</div>
<MudCheckBox T="bool" @bind-Value="_selectedItem.LimitToDateRange" Dense="true"/>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Start Day</MudText>
</div>
<MudSelect T="int" @bind-Value="_selectedItem.StartMonth" Disabled="@(!_selectedItem.LimitToDateRange)">
@foreach (int month in Enumerable.Range(1, 12))
{
<MudSelectItem Value="@month">@_dtf.GetMonthName(month)</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudSelect T="int" @bind-Value="_selectedItem.StartDay" Disabled="@(!_selectedItem.LimitToDateRange)">
@foreach (int day in Enumerable.Range(1, 31))
{
<MudSelectItem Value="@day">@day.ToString()</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudSelect T="int?" @bind-Value="_selectedItem.StartYear" Disabled="@(!_selectedItem.LimitToDateRange)" Clearable="true">
@foreach (int year in Enumerable.Range(DateTime.Today.Year, 50))
{
<MudSelectItem T="int?" Value="@year">@year.ToString()</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>End Day</MudText>
</div>
<MudSelect T="int" @bind-Value="_selectedItem.EndMonth" Disabled="@(!_selectedItem.LimitToDateRange)">
@foreach (int month in Enumerable.Range(1, 12))
{
<MudSelectItem Value="@month">@_dtf.GetMonthName(month)</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudSelect T="int" @bind-Value="_selectedItem.EndDay" Disabled="@(!_selectedItem.LimitToDateRange)">
@foreach (int day in Enumerable.Range(1, 31))
{
<MudSelectItem Value="@day">@day.ToString()</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudSelect T="int?" @bind-Value="_selectedItem.EndYear" Disabled="@(!_selectedItem.LimitToDateRange)" Clearable="true">
@foreach (int year in Enumerable.Range(DateTime.Today.Year, 50))
{
<MudSelectItem T="int?" Value="@year">@year.ToString()</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Days of the Week</MudText>
@@ -211,7 +276,14 @@
ProgramSchedule = _schedules.Find(vm => vm.Id == item.ProgramScheduleId),
DaysOfWeek = item.DaysOfWeek.OrderBy(x => ((int)x + 6) % 7).ToList(),
DaysOfMonth = item.DaysOfMonth.ToList(),
MonthsOfYear = item.MonthsOfYear.ToList()
MonthsOfYear = item.MonthsOfYear.ToList(),
LimitToDateRange = item.LimitToDateRange,
StartMonth = item.StartMonth,
StartDay = item.StartDay,
StartYear = item.StartYear,
EndMonth = item.EndMonth,
EndDay = item.EndDay,
EndYear = item.EndYear
};
private void SelectWeekdays()
@@ -243,9 +315,16 @@
{
Index = _items.Map(i => i.Index).DefaultIfEmpty().Max() + 1,
ProgramSchedule = _schedules.Head(),
DaysOfWeek = ProgramScheduleAlternate.AllDaysOfWeek(),
DaysOfMonth = ProgramScheduleAlternate.AllDaysOfMonth(),
MonthsOfYear = ProgramScheduleAlternate.AllMonthsOfYear()
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = false,
StartMonth = 1,
StartDay = 1,
StartYear = null,
EndMonth = 1,
EndDay = 1,
EndYear = null
};
_items.Add(item);
@@ -280,7 +359,14 @@
item.ProgramSchedule.Id,
item.DaysOfWeek,
item.DaysOfMonth,
item.MonthsOfYear)).ToList();
item.MonthsOfYear,
item.LimitToDateRange,
item.StartMonth,
item.StartDay,
item.StartYear,
item.EndMonth,
item.EndDay,
item.EndYear)).ToList();
Seq<BaseError> errorMessages = await Mediator.Send(new ReplacePlayoutAlternateScheduleItems(Id, items), _cts.Token)
.Map(e => e.LeftToSeq());
+29 -7
View File
@@ -3,7 +3,7 @@
@using System.Text
@using ErsatzTV.Application.Channels
@using ErsatzTV.Application.Scheduling
@using ErsatzTV.Core.Domain.Scheduling
@using ErsatzTV.Core.Scheduling
@implements IDisposable
@inject NavigationManager NavigationManager
@inject ILogger<PlayoutTemplatesEditor> Logger
@@ -38,7 +38,7 @@
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudText Typo="Typo.h5" Class="mb-2">@_channelName - Templates</MudText>
<MudDivider Class="mb-6"/>
<MudText Typo="Typo.body2" Class="mb-6">In priority order from top to bottom. The bottom entry will *always* match all days and all months, as a catch-all.</MudText>
<MudText Typo="Typo.body2" Class="mb-6">In priority order from top to bottom.</MudText>
<MudTable T="PlayoutTemplateEditViewModel" Class="mt-4" Hover="true" Items="_items.OrderBy(i => i.Index)" Dense="true" SelectedItem="@_selectedItem" SelectedItemChanged="@(vm => SelectedItemChanged(vm))" RowClassFunc="@SelectedRowClassFunc">
<ColGroup>
<MudHidden Breakpoint="Breakpoint.Xs">
@@ -172,6 +172,15 @@
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudSelect T="int?" @bind-Value="_selectedItem.StartYear" Disabled="@(!_selectedItem.LimitToDateRange)" Clearable="true">
@foreach (int year in Enumerable.Range(DateTime.Today.Year, 50))
{
<MudSelectItem T="int?" Value="@year">@year.ToString()</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>End Day</MudText>
@@ -192,6 +201,15 @@
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudSelect T="int?" @bind-Value="_selectedItem.EndYear" Disabled="@(!_selectedItem.LimitToDateRange)" Clearable="true">
@foreach (int year in Enumerable.Range(DateTime.Today.Year, 50))
{
<MudSelectItem T="int?" Value="@year">@year.ToString()</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Days of the Week</MudText>
@@ -322,8 +340,10 @@
LimitToDateRange = item.LimitToDateRange,
StartMonth = item.StartMonth,
StartDay = item.StartDay,
StartYear = item.StartYear,
EndMonth = item.EndMonth,
EndDay = item.EndDay
EndDay = item.EndDay,
EndYear = item.EndYear
};
private async Task UpdateTemplateGroupItems(TemplateGroupViewModel templateGroup)
@@ -385,9 +405,9 @@
var item = new PlayoutTemplateEditViewModel
{
Index = _items.Map(i => i.Index).DefaultIfEmpty().Max() + 1,
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear()
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear()
};
_items.Add(item);
@@ -433,8 +453,10 @@
item.LimitToDateRange,
item.StartMonth,
item.StartDay,
item.StartYear,
item.EndMonth,
item.EndDay)).ToList();
item.EndDay,
item.EndYear)).ToList();
Option<BaseError> maybeError = await Mediator.Send(new ReplacePlayoutTemplateItems(Id, items), _cts.Token);