Enhance timetable functionality and UI integration
- Updated protocol documentation to include new `classId` and `roomLabel` fields in the timetable API responses. - Added classes and rooms to the timetable response structure, improving data accessibility for client applications. - Enhanced the UI components to display timetable information, including class and room details, in the management and people panels. - Implemented functionality to fetch and display personal timetables for individuals, ensuring a comprehensive view of schedules. - Revised localization strings to support new timetable features and improve user experience. - Added tests to validate the new timetable functionalities and ensure robustness in handling timetable data.
This commit is contained in:
@@ -52,6 +52,7 @@ internal sealed record PersonCardResponse(
|
||||
IReadOnlyList<string> Roles,
|
||||
int? ClassYear,
|
||||
string? ClassLetter,
|
||||
string? ClassId,
|
||||
string? Position,
|
||||
string? PositionLabel,
|
||||
IReadOnlyList<LabeledStatResponse> Body,
|
||||
|
||||
@@ -111,10 +111,18 @@ internal static class TimetableEndpoints
|
||||
var roster = published.Roster ?? new Roster([], [], []);
|
||||
if (published.Catalog is null)
|
||||
{
|
||||
return new TimetableResponse(weekDays, 0, [], []);
|
||||
return new TimetableResponse(weekDays, 0, [], [], [], []);
|
||||
}
|
||||
|
||||
return TimetableMapper.From(table, roster, published.Catalog, weekDays, locale, classId, personId);
|
||||
return TimetableMapper.From(
|
||||
table,
|
||||
roster,
|
||||
published.Catalog,
|
||||
published.Map,
|
||||
weekDays,
|
||||
locale,
|
||||
classId,
|
||||
personId);
|
||||
}
|
||||
|
||||
private static IResult TimetableResult(
|
||||
|
||||
@@ -29,7 +29,9 @@ internal sealed record TimetableResponse(
|
||||
int WeekDays,
|
||||
int LessonCount,
|
||||
IReadOnlyList<TimetableLessonResponse> Lessons,
|
||||
IReadOnlyList<UncoveredLessonResponse> Uncovered);
|
||||
IReadOnlyList<UncoveredLessonResponse> Uncovered,
|
||||
IReadOnlyList<TimetableClassResponse> Classes,
|
||||
IReadOnlyList<TimetableRoomResponse> Rooms);
|
||||
|
||||
internal sealed record TimetableLessonResponse(
|
||||
string ClassId,
|
||||
@@ -40,6 +42,7 @@ internal sealed record TimetableLessonResponse(
|
||||
string TeacherId,
|
||||
string TeacherName,
|
||||
string RoomId,
|
||||
string RoomLabel,
|
||||
int Day,
|
||||
int Period,
|
||||
bool Locked);
|
||||
@@ -52,12 +55,17 @@ internal sealed record UncoveredLessonResponse(
|
||||
string SubjectLabel,
|
||||
int Hours);
|
||||
|
||||
internal sealed record TimetableClassResponse(string Id, int Year, string Letter);
|
||||
|
||||
internal sealed record TimetableRoomResponse(string Id, string Label);
|
||||
|
||||
internal static class TimetableMapper
|
||||
{
|
||||
public static TimetableResponse From(
|
||||
Timetable table,
|
||||
Roster roster,
|
||||
DefCatalog catalog,
|
||||
MapLayout? map,
|
||||
int weekDays,
|
||||
string locale,
|
||||
string? classId,
|
||||
@@ -65,6 +73,7 @@ internal static class TimetableMapper
|
||||
{
|
||||
var people = roster.People.ToDictionary(person => person.Id, StringComparer.Ordinal);
|
||||
var classes = roster.Classes.ToDictionary(item => item.Id, StringComparer.Ordinal);
|
||||
var roomNames = RoomNames(catalog, map, locale);
|
||||
var lessons = table.Lessons.AsEnumerable();
|
||||
var uncovered = table.Uncovered.AsEnumerable();
|
||||
if (!string.IsNullOrWhiteSpace(classId))
|
||||
@@ -81,8 +90,15 @@ internal static class TimetableMapper
|
||||
return new TimetableResponse(
|
||||
weekDays,
|
||||
catalog.DayFrame?.LessonCount ?? 0,
|
||||
lessons.Select(lesson => MapLesson(lesson, classes, people, catalog, locale)).ToArray(),
|
||||
uncovered.Select(row => MapUncovered(row, classes, catalog, locale)).ToArray());
|
||||
lessons.Select(lesson => MapLesson(lesson, classes, people, catalog, locale, roomNames)).ToArray(),
|
||||
uncovered.Select(row => MapUncovered(row, classes, catalog, locale)).ToArray(),
|
||||
roster.Classes
|
||||
.OrderBy(item => item.Year)
|
||||
.ThenBy(item => item.Letter, StringComparer.Ordinal)
|
||||
.ThenBy(item => item.Id, StringComparer.Ordinal)
|
||||
.Select(item => new TimetableClassResponse(item.Id, item.Year, item.Letter))
|
||||
.ToArray(),
|
||||
roomNames.Select(pair => new TimetableRoomResponse(pair.Key, pair.Value)).ToArray());
|
||||
}
|
||||
|
||||
private static TimetableLessonResponse MapLesson(
|
||||
@@ -90,7 +106,8 @@ internal static class TimetableMapper
|
||||
IReadOnlyDictionary<string, SchoolClass> classes,
|
||||
IReadOnlyDictionary<string, Person> people,
|
||||
DefCatalog catalog,
|
||||
string locale)
|
||||
string locale,
|
||||
IReadOnlyDictionary<string, string> roomNames)
|
||||
{
|
||||
classes.TryGetValue(lesson.ClassId, out var schoolClass);
|
||||
people.TryGetValue(lesson.TeacherId, out var teacher);
|
||||
@@ -107,6 +124,7 @@ internal static class TimetableMapper
|
||||
lesson.TeacherId,
|
||||
teacher?.Name.Full ?? lesson.TeacherId,
|
||||
lesson.RoomId,
|
||||
roomNames.GetValueOrDefault(lesson.RoomId, lesson.RoomId),
|
||||
lesson.Day,
|
||||
lesson.Period,
|
||||
lesson.Locked);
|
||||
@@ -131,4 +149,18 @@ internal static class TimetableMapper
|
||||
subjectLabel,
|
||||
row.Hours);
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, string> RoomNames(DefCatalog catalog, MapLayout? map, string locale)
|
||||
{
|
||||
if (map is null)
|
||||
{
|
||||
return new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
return MapView.Build(catalog, map, locale)
|
||||
.Where(node => node.Kind == MapNodeKind.Room)
|
||||
.OrderBy(node => node.Name, StringComparer.Ordinal)
|
||||
.ThenBy(node => node.Id, StringComparer.Ordinal)
|
||||
.ToDictionary(node => node.Id, node => node.Name, StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ internal sealed class GameLoopService(
|
||||
worker.RosterSnapshot,
|
||||
worker.ApplicantSnapshot,
|
||||
worker.CatalogSnapshot,
|
||||
worker.TimetableSnapshot);
|
||||
worker.TimetableSnapshot,
|
||||
worker.MapSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,4 +644,5 @@ internal sealed record PublishedSchoolPeople(
|
||||
Roster? Roster,
|
||||
ApplicantPool? Applicants,
|
||||
DefCatalog? Catalog,
|
||||
Timetable? Timetable);
|
||||
Timetable? Timetable,
|
||||
MapLayout? Map);
|
||||
|
||||
@@ -54,6 +54,7 @@ internal static class PersonCardReader
|
||||
PeopleListMapper.RolesOf(person),
|
||||
year,
|
||||
letter,
|
||||
person.ClassId,
|
||||
person.Position,
|
||||
PeopleListMapper.PositionLabel(catalog, locale, person.Position),
|
||||
Body(person, catalog, locale),
|
||||
|
||||
@@ -46,6 +46,7 @@ internal sealed class SchoolWorker
|
||||
private ApplicantPool? _applicantSnapshot;
|
||||
private DefCatalog? _catalogSnapshot;
|
||||
private Timetable? _timetableSnapshot;
|
||||
private MapLayout? _mapSnapshot;
|
||||
private OccupancyKey _occupancyKey;
|
||||
private School? _school;
|
||||
private Task? _run;
|
||||
@@ -109,6 +110,9 @@ internal sealed class SchoolWorker
|
||||
/// <summary>Last built timetable. Published like the roster — HTTP never reads the live school.</summary>
|
||||
public Timetable? TimetableSnapshot => Volatile.Read(ref _timetableSnapshot);
|
||||
|
||||
/// <summary>Frozen map instance. Safe to read from HTTP with the catalog; it does not mutate.</summary>
|
||||
public MapLayout? MapSnapshot => Volatile.Read(ref _mapSnapshot);
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_run = Task.Factory.StartNew(
|
||||
@@ -205,6 +209,7 @@ internal sealed class SchoolWorker
|
||||
var catalog = _mods.LoadCatalog(packIds, _logger);
|
||||
Volatile.Write(ref _catalogSnapshot, catalog);
|
||||
var map = _mods.LoadMap(packIds, _savedMap);
|
||||
Volatile.Write(ref _mapSnapshot, map);
|
||||
try
|
||||
{
|
||||
MapValidator.Validate(map, catalog);
|
||||
@@ -565,6 +570,7 @@ internal sealed class SchoolWorker
|
||||
Volatile.Write(ref _rosterSnapshot, school.Roster);
|
||||
Volatile.Write(ref _applicantSnapshot, school.Applicants);
|
||||
Volatile.Write(ref _timetableSnapshot, school.Timetable);
|
||||
Volatile.Write(ref _mapSnapshot, school.Map);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user