feat: read "Занятия в Полоцке" as a day's town, show it in all clients
- Parser 14 (server/places.js): "Занятия в Полоцке/Новополоцке/г. Новополоцк" over a day's first row is no longer a lesson but a note of the day for its column (`places`); lessons of that day get `place`. A lesson below the note in the same cell keeps its own subject.
- A town on its own line ("Новополоцк", "г.Новополоцк") or after a room ("246(Новополоцк)") becomes the lesson's `place` and leaves the subject and room. "История Полоцка" is not a town.
- API 14: `place` on lessons and `places` on days of /api/timetable, `place` in the directory. The day notes are shared by the server and the PWA (shared/places.js); the same note in every subgroup reads as the whole group's.
- Android, PWA and iOS: a "Занятия в Полоцке" card above the day's lessons (day pages, calendar, overview, week view, desk stand), "Полоцк" under the date in the week grid, "249Ж · Полоцк" on lesson cards, a "Город" row in details, the town in widgets, tile, reminders and change lists.
- Snapshot lines show the town and the day notes; tests for notes, towns in cells and the API.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,11 @@ final class AppModel {
|
||||
t.slots(on: date, subgroup: subgroup, hidden: hidden)
|
||||
}
|
||||
|
||||
/** "Занятия в Полоцке" over the date, for the student's subgroup. */
|
||||
func places(_ t: Timetable, on date: CivilDate) -> [DayPlace] {
|
||||
t.places(on: date, subgroup: subgroup, hidden: hidden)
|
||||
}
|
||||
|
||||
/** The open group's last republished document: what changed for it. */
|
||||
var changes: ScheduleChanges? {
|
||||
guard let t = timetable else { return nil }
|
||||
|
||||
@@ -35,10 +35,11 @@ enum Reminders {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = slot.lessons.map(\.subject).joined(separator: " / ")
|
||||
let first = slot.lessons[0]
|
||||
let place = Array(Set(slot.lessons.flatMap(\.rooms))).sorted().joined(separator: ", ")
|
||||
let rooms = Array(Set(slot.lessons.flatMap(\.rooms))).sorted().joined(separator: ", ")
|
||||
let place = withPlace(rooms.isEmpty ? nil : "ауд. " + rooms, slot.lessons.compactMap(\.place).first)
|
||||
var text = slot.start.description
|
||||
if let pair = first.pair { text += " · \(pair) пара" }
|
||||
if !place.isEmpty { text += " · ауд. \(place)" }
|
||||
if let place { text += " · \(place)" }
|
||||
if slot.lessons.contains(where: \.uncertain) { text += " · сверьте недели" }
|
||||
content.body = text
|
||||
content.sound = .default
|
||||
|
||||
@@ -225,6 +225,7 @@ private struct DayAgenda: View {
|
||||
}
|
||||
if !slots.isEmpty {
|
||||
Text("Ваши занятия").font(.headline)
|
||||
if let t = model.timetable { DayPlaces(places: model.places(t, on: date)) }
|
||||
ForEach(slots) { slot in
|
||||
ForEach(slot.lessons, id: \.key) { lesson in
|
||||
Button { details = LessonRef(lesson: lesson, date: date) } label: {
|
||||
|
||||
@@ -79,7 +79,7 @@ struct ChangesSheet: View {
|
||||
Section("Добавлено · \(record.added.count)") {
|
||||
ForEach(record.added, id: \.self) { row in
|
||||
item(row, details: [row.lesson.type, row.lesson.teachers.joined(separator: ", "),
|
||||
row.lesson.rooms.map { "ауд. \($0)" }.joined(separator: ", "), row.lesson.condition])
|
||||
withPlace(row.lesson.rooms.map { "ауд. \($0)" }.joined(separator: ", "), row.lesson.place) ?? "", row.lesson.condition])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +142,55 @@ struct Banner: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// "Занятия в Полоцке" above a day's lessons: Word marks the days a group spends in another
|
||||
/// town, and every lesson of that day is there.
|
||||
struct DayPlaces: View {
|
||||
var places: [DayPlace]
|
||||
|
||||
var body: some View {
|
||||
if !places.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
ForEach(places, id: \.self) { p in
|
||||
HStack(spacing: 14) {
|
||||
Image(systemName: "building.2")
|
||||
.font(.title3)
|
||||
.foregroundStyle(Color.indigo)
|
||||
.accessibilityHidden(true)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(p.text).font(.headline)
|
||||
Text(p.subgroup.map { "\($0) · пометка в Word на этот день" } ?? "Пометка в Word на этот день")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 12)
|
||||
.background(Color.indigo.opacity(0.14), in: .rect(cornerRadius: 18, style: .continuous))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The town under a day's date in the week grid: short, in the note's colour.
|
||||
struct DayPlaceMark: View {
|
||||
var places: [DayPlace]
|
||||
|
||||
var body: some View {
|
||||
if let first = places.first {
|
||||
Text(first.place)
|
||||
.font(.caption2.weight(.semibold))
|
||||
.lineLimit(1)
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 1)
|
||||
.foregroundStyle(Color.indigo)
|
||||
.background(Color.indigo.opacity(0.14), in: .rect(cornerRadius: 4, style: .continuous))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The status banner of the app model, if it has something to say.
|
||||
struct StatusBanner: View {
|
||||
@Environment(AppModel.self) private var model
|
||||
|
||||
@@ -90,7 +90,7 @@ struct HomeView: View {
|
||||
WeekReport(timetable: t)
|
||||
SessionCard(timetable: t) { session = true }
|
||||
DayTitle(label: "Сегодня", date: today)
|
||||
TodayCard(slots: todaySlots, onOpen: { details = LessonRef(lesson: $0, date: today) }, onDay: { router.openDay(today) })
|
||||
TodayCard(slots: todaySlots, places: model.places(t, on: today), onOpen: { details = LessonRef(lesson: $0, date: today) }, onDay: { router.openDay(today) })
|
||||
AttendanceCard(slots: todaySlots)
|
||||
NavigationLink(value: HomeRoute.subjects) { SubjectsLink(timetable: t) }
|
||||
.buttonStyle(.plain)
|
||||
@@ -99,6 +99,7 @@ struct HomeView: View {
|
||||
let (day, slots) = next
|
||||
DayTitle(label: day == today.adding(days: 1) ? "Завтра" : day.weekdayName, date: day)
|
||||
SectionCard(symbol: "calendar.badge.checkmark", title: pairs(slots.count)) {
|
||||
DayPlaces(places: model.places(t, on: day))
|
||||
ForEach(slots) { slot in
|
||||
ForEach(slot.lessons, id: \.key) { lesson in
|
||||
Button { details = LessonRef(lesson: lesson, date: day) } label: { CompactLesson(lesson: lesson, slot: slot) }
|
||||
@@ -264,6 +265,7 @@ private struct DayChip: View {
|
||||
private struct TodayCard: View {
|
||||
@Environment(AppModel.self) private var model
|
||||
var slots: [Slot]
|
||||
var places: [DayPlace]
|
||||
var onOpen: (Lesson) -> Void
|
||||
var onDay: () -> Void
|
||||
|
||||
@@ -276,6 +278,7 @@ private struct TodayCard: View {
|
||||
} else if left.isEmpty {
|
||||
Quiet(title: "Пары на сегодня закончились", text: "Сегодня было \(pairs(slots.count)).")
|
||||
} else {
|
||||
DayPlaces(places: places)
|
||||
let first = left[0]
|
||||
ForEach(first.lessons, id: \.key) { lesson in
|
||||
Button { onOpen(lesson) } label: { FeaturedLesson(lesson: lesson, slot: first, clock: clock) }
|
||||
@@ -328,7 +331,9 @@ private struct FeaturedLesson: View {
|
||||
.background(running ? Color.brand : Color.brand.opacity(0.14), in: .capsule)
|
||||
}
|
||||
if lesson.distance { Meta(symbol: "mappin.and.ellipse", text: "Дистанционно") }
|
||||
else if show.rooms && !lesson.rooms.isEmpty { Meta(symbol: "mappin.and.ellipse", text: "Ауд. " + lesson.rooms.joined(separator: ", ")) }
|
||||
else if let room = withPlace(show.rooms && !lesson.rooms.isEmpty ? "Ауд. " + lesson.rooms.joined(separator: ", ") : nil, lesson.place) {
|
||||
Meta(symbol: "mappin.and.ellipse", text: room)
|
||||
}
|
||||
if show.teachers && !lesson.teachers.isEmpty { Meta(symbol: "person", text: lesson.teachers.joined(separator: ", ")) }
|
||||
}
|
||||
Spacer(minLength: 0)
|
||||
|
||||
@@ -38,8 +38,11 @@ struct LessonCard: View {
|
||||
}
|
||||
FlowLayout(spacing: 14) {
|
||||
if lesson.distance { Meta(symbol: "mappin.and.ellipse", text: "Дистанционно") }
|
||||
else if show.rooms && !lesson.rooms.isEmpty {
|
||||
Meta(symbol: "mappin.and.ellipse", text: lesson.rooms.joined(separator: ", ") + (lesson.roomMoved ? " (перенос)" : ""))
|
||||
else if let room = withPlace(
|
||||
show.rooms && !lesson.rooms.isEmpty ? lesson.rooms.joined(separator: ", ") + (lesson.roomMoved ? " (перенос)" : "") : nil,
|
||||
lesson.place
|
||||
) {
|
||||
Meta(symbol: "mappin.and.ellipse", text: room)
|
||||
}
|
||||
if show.teachers && !lesson.teachers.isEmpty { Meta(symbol: "person", text: lesson.teachers.joined(separator: ", ")) }
|
||||
if let sub = lesson.subgroup { Meta(symbol: "person.2", text: sub) }
|
||||
@@ -123,7 +126,7 @@ struct CompactLesson: View {
|
||||
if let session = lesson.session { parts.insert(session.label, at: 0) }
|
||||
if let sub = lesson.subgroup, lesson.session != nil { parts.append(sub.lowercased().replacingOccurrences(of: "подгруппа", with: "подгр.")) }
|
||||
if lesson.distance { parts.append("дистанционно") }
|
||||
else if show.rooms && !lesson.rooms.isEmpty { parts.append("ауд. " + lesson.rooms.joined(separator: ", ")) }
|
||||
else if let room = withPlace(show.rooms && !lesson.rooms.isEmpty ? "ауд. " + lesson.rooms.joined(separator: ", ") : nil, lesson.place) { parts.append(room) }
|
||||
if show.tags && !lesson.type.isEmpty && lesson.session == nil { parts.append(lesson.type.lowercased()) }
|
||||
return parts.joined(separator: " · ")
|
||||
}
|
||||
@@ -163,6 +166,9 @@ struct LessonDetails: View {
|
||||
value: lesson.distance ? "Дистанционно" : (lesson.rooms.isEmpty ? "Не указана" : lesson.rooms.joined(separator: ", ")),
|
||||
extra: lesson.roomMoved ? "В этот день — перенос аудитории" : nil
|
||||
)
|
||||
if let place = lesson.place {
|
||||
DetailRow(symbol: "building.2", label: "Город", value: place, extra: "Так указано в Word")
|
||||
}
|
||||
DetailRow(symbol: "person.3", label: "Группы", value: lesson.groups.joined(separator: ", "), extra: lesson.subgroup)
|
||||
}
|
||||
.card()
|
||||
|
||||
@@ -153,6 +153,7 @@ private struct DayPage: View {
|
||||
EmptyState(title: "Занятий нет", text: "По таблице недель в этот день свободно — можно отдохнуть.", symbol: "sofa")
|
||||
.padding(.top, 60)
|
||||
}
|
||||
DayPlaces(places: model.places(timetable, on: date))
|
||||
ForEach(slots) { slot in
|
||||
let running = isToday && clock >= slot.start && clock < slot.end
|
||||
let status: String? = running
|
||||
@@ -281,6 +282,7 @@ private struct WordRows: View {
|
||||
let lessons = day.lessons
|
||||
.filter { model.subgroup == nil || $0.subgroup == nil || $0.subgroup == model.subgroup }
|
||||
.filter { !model.hidden.contains($0.hideKey) }
|
||||
if !lessons.isEmpty { DayPlaces(places: day.places.forSubgroup(model.subgroup)) }
|
||||
ForEach(lessons, id: \.key) { lesson in
|
||||
Button { onOpen(lesson) } label: {
|
||||
LessonCard(lesson: lesson, time: lesson.start + (lesson.end.map { " → \($0)" } ?? ""), date: nil)
|
||||
|
||||
@@ -165,7 +165,7 @@ private struct DirectoryLessonCard: View {
|
||||
Text(lesson.start + (lesson.end.map { " → \($0)" } ?? "") + (pair.map { " · \($0) пара" } ?? "")).font(.body.monospacedDigit())
|
||||
FlowLayout(spacing: 14) {
|
||||
if case .teacher = target {
|
||||
Meta(symbol: "mappin.and.ellipse", text: lesson.distance ? "Дистанционно" : lesson.rooms.joined(separator: ", ") + (lesson.moved ? " (перенос)" : ""))
|
||||
Meta(symbol: "mappin.and.ellipse", text: lesson.distance ? "Дистанционно" : withPlace(lesson.rooms.joined(separator: ", ") + (lesson.moved ? " (перенос)" : ""), lesson.place) ?? "")
|
||||
} else if !lesson.teachers.isEmpty {
|
||||
Meta(symbol: "person", text: lesson.teachers.joined(separator: ", "))
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ private struct SubjectCard: View {
|
||||
}
|
||||
if let first = summary.upcoming.first {
|
||||
let (slot, lesson) = first
|
||||
Meta(symbol: "clock", text: nextLabel(slot) + (lesson.rooms.isEmpty ? "" : " · ауд. " + lesson.rooms.joined(separator: ", ")))
|
||||
Meta(symbol: "clock", text: nextLabel(slot) + (withPlace(lesson.rooms.isEmpty ? nil : "ауд. " + lesson.rooms.joined(separator: ", "), lesson.place).map { " · " + $0 } ?? ""))
|
||||
} else {
|
||||
Meta(symbol: "checkmark.circle", text: "Занятий в этом семестре больше нет")
|
||||
}
|
||||
@@ -211,7 +211,7 @@ private struct SubjectDetails: View {
|
||||
Button { details = LessonRef(lesson: item.1, date: item.0.date) } label: {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(item.0.date.weekdayName + ", " + item.0.date.longLabel).font(.subheadline.weight(.semibold))
|
||||
Text([item.0.timeLabel, item.1.type.isEmpty ? nil : item.1.type.lowercased(), item.1.rooms.isEmpty ? nil : "ауд. " + item.1.rooms.joined(separator: ", ")].compactMap { $0 }.joined(separator: " · "))
|
||||
Text([item.0.timeLabel, item.1.type.isEmpty ? nil : item.1.type.lowercased(), withPlace(item.1.rooms.isEmpty ? nil : "ауд. " + item.1.rooms.joined(separator: ", "), item.1.place)].compactMap { $0 }.joined(separator: " · "))
|
||||
.font(.subheadline).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,13 @@ struct WeekGrid: View {
|
||||
let today = model.today
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
// Dates stay on one line when a day has the town under it.
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Color.clear.frame(width: gutter, height: 1)
|
||||
ForEach(days, id: \.0) { day in
|
||||
let d = day.0
|
||||
let isToday = d == today
|
||||
let places = model.places(timetable, on: d)
|
||||
VStack(spacing: 2) {
|
||||
Text(d.shortWeekday).font(.caption.weight(.medium)).foregroundStyle(isToday ? Color.brand : .secondary)
|
||||
Text("\(d.day)")
|
||||
@@ -33,10 +35,12 @@ struct WeekGrid: View {
|
||||
.foregroundStyle(isToday ? Color.onBrand : .primary)
|
||||
.frame(width: 34, height: 34)
|
||||
.background(isToday ? Color.brand : .clear, in: .circle)
|
||||
// "Занятия в Полоцке": the town of the whole day, under its date.
|
||||
DayPlaceMark(places: places)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel(d.weekdayName + ", " + d.longLabel)
|
||||
.accessibilityLabel(d.weekdayName + ", " + d.longLabel + places.map { ", " + $0.text }.joined())
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 8)
|
||||
@@ -117,7 +121,8 @@ private struct LessonBlock: View {
|
||||
Text(wide ? lesson.subject : abbreviate(lesson.subject))
|
||||
.font(.caption.weight(.semibold))
|
||||
.lineLimit(wide ? 3 : 2)
|
||||
if show.rooms, let room = lesson.distance ? "дист." : lesson.rooms.first {
|
||||
// A narrow column keeps the room whole: the day's town is under its date.
|
||||
if let room = lesson.distance ? (show.rooms ? "дист." : nil) : (wide ? withPlace(show.rooms ? lesson.rooms.first : nil, lesson.place) : (show.rooms ? lesson.rooms.first : nil) ?? lesson.place) {
|
||||
Text(room).font(.caption2).lineLimit(1).opacity(0.9)
|
||||
}
|
||||
Spacer(minLength: 0)
|
||||
@@ -140,6 +145,6 @@ private struct LessonBlock: View {
|
||||
}
|
||||
}
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel((lesson.session.map { "\($0.label): " } ?? "") + "\(lesson.subject), \(lesson.start)" + (lesson.rooms.isEmpty ? "" : ", ауд. " + lesson.rooms.joined(separator: ", ")))
|
||||
.accessibilityLabel((lesson.session.map { "\($0.label): " } ?? "") + "\(lesson.subject), \(lesson.start)" + (withPlace(lesson.rooms.isEmpty ? nil : "ауд. " + lesson.rooms.joined(separator: ", "), lesson.place).map { ", " + $0 } ?? ""))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ private func fields(_ a: ChangedLesson, _ b: ChangedLesson) -> [FieldChange] {
|
||||
("Вид занятия", a.lesson.type, b.lesson.type),
|
||||
("Преподаватель", a.lesson.teachers.joined(separator: ", "), b.lesson.teachers.joined(separator: ", ")),
|
||||
("Аудитория", a.lesson.rooms.joined(separator: ", "), b.lesson.rooms.joined(separator: ", ")),
|
||||
("Город", a.lesson.place ?? "", b.lesson.place ?? ""),
|
||||
]
|
||||
let shown = { (s: String) in s.trimmed.isEmpty ? "не указано" : s }
|
||||
return pairs.filter { $0.1.trimmed != $0.2.trimmed }.map { FieldChange(label: $0.0, from: shown($0.1), to: shown($0.2)) }
|
||||
|
||||
@@ -51,11 +51,39 @@ struct Day: Codable, Hashable {
|
||||
var weekday: Int
|
||||
var week: Week?
|
||||
var lessons: [Lesson] = []
|
||||
var places: [DayPlace] = []
|
||||
}
|
||||
|
||||
struct WeeklyDay: Codable, Hashable {
|
||||
var day: Int
|
||||
var lessons: [Lesson] = []
|
||||
var places: [DayPlace] = []
|
||||
}
|
||||
|
||||
/**
|
||||
* "Занятия в Полоцке" over a day in Word: that day the group's lessons (or one subgroup's,
|
||||
* when `subgroup` is set) are in another town (server/timetable.js, shared/places.js).
|
||||
*/
|
||||
struct DayPlace: Codable, Hashable {
|
||||
var place: String
|
||||
var text: String
|
||||
var subgroup: String?
|
||||
}
|
||||
|
||||
extension Array where Element == DayPlace {
|
||||
/** The notes for the student's subgroup; a note for their own subgroup reads as the whole day's. */
|
||||
func forSubgroup(_ subgroup: String?) -> [DayPlace] {
|
||||
var seen = Set<DayPlace>()
|
||||
return filter { subgroup == nil || $0.subgroup == nil || $0.subgroup == subgroup }
|
||||
.map { subgroup == nil ? $0 : DayPlace(place: $0.place, text: $0.text, subgroup: nil) }
|
||||
.filter { seen.insert($0).inserted }
|
||||
}
|
||||
}
|
||||
|
||||
/** A room label with the lesson's town when the document names one: "ауд. 203Г · Полоцк", or the town alone. */
|
||||
func withPlace(_ room: String?, _ place: String?) -> String? {
|
||||
let parts = [room, place].compactMap { $0 }.filter { !$0.isEmpty }
|
||||
return parts.isEmpty ? nil : parts.joined(separator: " · ")
|
||||
}
|
||||
|
||||
struct Lesson: Codable, Hashable {
|
||||
@@ -68,6 +96,8 @@ struct Lesson: Codable, Hashable {
|
||||
var teachers: [String] = []
|
||||
var rooms: [String] = []
|
||||
var roomMoved: Bool = false
|
||||
/** The town, when the cell or the day's note names one: "Полоцк". */
|
||||
var place: String?
|
||||
var distance: Bool = false
|
||||
var condition: String = ""
|
||||
var variant: String = "all"
|
||||
@@ -139,6 +169,7 @@ struct DirectoryLesson: Codable, Hashable {
|
||||
var teachers: [String] = []
|
||||
var rooms: [String] = []
|
||||
var moved: Bool = false
|
||||
var place: String?
|
||||
var distance: Bool = false
|
||||
var groups: [String] = []
|
||||
var condition: String = ""
|
||||
@@ -266,6 +297,7 @@ extension Day {
|
||||
weekday = try c.decodeIfPresent(Int.self, forKey: .weekday) ?? 0
|
||||
week = try c.decodeIfPresent(Week.self, forKey: .week)
|
||||
lessons = try c.decodeIfPresent([Lesson].self, forKey: .lessons) ?? []
|
||||
places = try c.decodeIfPresent([DayPlace].self, forKey: .places) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +306,16 @@ extension WeeklyDay {
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
day = try c.decode(Int.self, forKey: .day)
|
||||
lessons = try c.decodeIfPresent([Lesson].self, forKey: .lessons) ?? []
|
||||
places = try c.decodeIfPresent([DayPlace].self, forKey: .places) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
extension DayPlace {
|
||||
init(from decoder: Decoder) throws {
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
place = try c.decodeIfPresent(String.self, forKey: .place) ?? ""
|
||||
text = try c.decodeIfPresent(String.self, forKey: .text) ?? ""
|
||||
subgroup = try c.decodeIfPresent(String.self, forKey: .subgroup)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,6 +331,7 @@ extension Lesson {
|
||||
teachers = try c.decodeIfPresent([String].self, forKey: .teachers) ?? []
|
||||
rooms = try c.decodeIfPresent([String].self, forKey: .rooms) ?? []
|
||||
roomMoved = try c.decodeIfPresent(Bool.self, forKey: .roomMoved) ?? false
|
||||
place = try c.decodeIfPresent(String.self, forKey: .place)
|
||||
distance = try c.decodeIfPresent(Bool.self, forKey: .distance) ?? false
|
||||
condition = try c.decodeIfPresent(String.self, forKey: .condition) ?? ""
|
||||
variant = try c.decodeIfPresent(String.self, forKey: .variant) ?? "all"
|
||||
@@ -375,6 +418,7 @@ extension DirectoryLesson {
|
||||
teachers = try c.decodeIfPresent([String].self, forKey: .teachers) ?? []
|
||||
rooms = try c.decodeIfPresent([String].self, forKey: .rooms) ?? []
|
||||
moved = try c.decodeIfPresent(Bool.self, forKey: .moved) ?? false
|
||||
place = try c.decodeIfPresent(String.self, forKey: .place)
|
||||
distance = try c.decodeIfPresent(Bool.self, forKey: .distance) ?? false
|
||||
groups = try c.decodeIfPresent([String].self, forKey: .groups) ?? []
|
||||
condition = try c.decodeIfPresent(String.self, forKey: .condition) ?? ""
|
||||
|
||||
@@ -34,6 +34,13 @@ extension Timetable {
|
||||
.filter { !hidden.contains($0.hideKey) }
|
||||
}
|
||||
|
||||
/// "Занятия в Полоцке" on the date for the student's subgroup, only on a day they have lessons.
|
||||
func places(on date: CivilDate, subgroup: String?, hidden: Set<String>) -> [DayPlace] {
|
||||
guard let day = day(date), !day.places.isEmpty,
|
||||
!lessons(on: date, subgroup: subgroup, hidden: hidden).isEmpty else { return [] }
|
||||
return day.places.forSubgroup(subgroup)
|
||||
}
|
||||
|
||||
func slots(on date: CivilDate, subgroup: String?, hidden: Set<String>) -> [Slot] {
|
||||
let lessons = lessons(on: date, subgroup: subgroup, hidden: hidden)
|
||||
var order: [String] = []
|
||||
|
||||
@@ -70,7 +70,7 @@ private func status(_ slot: Slot, day: CivilDate, at date: Date) -> String? {
|
||||
|
||||
private func place(_ lesson: Lesson) -> String? {
|
||||
if lesson.distance { return "дист." }
|
||||
return lesson.rooms.isEmpty ? nil : "ауд. " + lesson.rooms.joined(separator: ", ")
|
||||
return withPlace(lesson.rooms.isEmpty ? nil : "ауд. " + lesson.rooms.joined(separator: ", "), lesson.place)
|
||||
}
|
||||
|
||||
/// "Сегодня": the day's lessons ahead, with the current one marked.
|
||||
|
||||
Reference in New Issue
Block a user