feat: department display

This commit is contained in:
2026-10-08 02:10:26 +03:00
parent c7298c0c84
commit 57e5bd9af8
5 changed files with 62 additions and 20 deletions
@@ -0,0 +1,10 @@
package agzam.vyatsu.data.local
import androidx.room.Embedded
/** Занятие преподавателя + кафедра-источник. Проекция LEFT JOIN, схема Room не меняется. */
data class TeacherLessonWithDepartment(
@Embedded val lesson: TeacherLessonEntity,
val departmentId: String,
val departmentName: String?
)
@@ -61,13 +61,15 @@ interface TeacherScheduleDao {
fun observeDaysForPeriod(season: String, weekStart: Long): Flow<List<TeacherDayEntity>>
@Query(
"SELECT teacher_lessons.* FROM teacher_lessons INNER JOIN teacher_days ON teacher_days.id = teacher_lessons.dayId " +
"SELECT teacher_lessons.*, teacher_schedules.departmentId AS departmentId, departments.name AS departmentName " +
"FROM teacher_lessons INNER JOIN teacher_days ON teacher_days.id = teacher_lessons.dayId " +
"INNER JOIN teacher_schedules ON teacher_schedules.id = teacher_days.scheduleId " +
"LEFT JOIN departments ON departments.departmentId = teacher_schedules.departmentId " +
"WHERE teacher_schedules.season = :season AND teacher_schedules.weekStart <= :weekStart " +
"AND teacher_schedules.weekEnd >= :weekStart " +
"ORDER BY teacher_lessons.date, teacher_lessons.slotIndex, teacher_lessons.lessonIndex"
)
fun observeLessonsForPeriod(season: String, weekStart: Long): Flow<List<TeacherLessonEntity>>
fun observeLessonsForPeriod(season: String, weekStart: Long): Flow<List<TeacherLessonWithDepartment>>
@Query(
"SELECT DISTINCT teacher FROM teacher_lessons INNER JOIN teacher_days ON teacher_days.id = teacher_lessons.dayId " +
@@ -4,6 +4,7 @@ import agzam.vyatsu.data.local.DepartmentPeriodEntity
import agzam.vyatsu.data.local.TeacherDao
import agzam.vyatsu.data.local.TeacherDayEntity
import agzam.vyatsu.data.local.TeacherLessonEntity
import agzam.vyatsu.data.local.TeacherLessonWithDepartment
import agzam.vyatsu.data.local.TeacherScheduleDao
import agzam.vyatsu.data.local.TeacherScheduleEntity
import agzam.vyatsu.data.remote.TeacherScheduleParser
@@ -238,30 +239,30 @@ class TeacherScheduleRepositoryImpl(
private fun buildTeacherSchedule(
period: SchedulePeriod,
dayEntities: List<TeacherDayEntity>,
lessonEntities: List<TeacherLessonEntity>
lessons: List<TeacherLessonWithDepartment>
): TeacherSchedule? {
val weekDays = dayEntities
.filter {
it.date >= period.weekStart.toEpochDay() && it.date <= period.weekEnd.toEpochDay()
}
if (weekDays.isEmpty()) return null
val lessonsByDate = lessonEntities.groupBy { it.date }
val lessonsByDate = lessons.groupBy { it.lesson.date }
val days = weekDays
.sortedBy { it.date }
.distinctBy { it.date }
.map { day ->
val lessons = lessonsByDate[day.date].orEmpty()
val dayLessons = lessonsByDate[day.date].orEmpty()
Log.d(
TAG,
"build: date=${day.date} totalLessons=${lessons.size} " +
"groupedByStart=${lessons.groupBy { it.start }.keys.sorted()}"
"build: date=${day.date} totalLessons=${dayLessons.size} " +
"groupedByStart=${dayLessons.groupBy { it.lesson.start }.keys.sorted()}"
)
val slots = lessons.groupBy { it.start }
val slots = dayLessons.groupBy { it.lesson.start }
.toSortedMap()
.map { (startMin, slotLessons) ->
val first = slotLessons.first()
val times = slotLessons.map { "${it.start}-${it.end}" }.distinct()
val teachers = slotLessons.map { it.teacher }.distinct()
val first = slotLessons.first().lesson
val times = slotLessons.map { "${it.lesson.start}-${it.lesson.end}" }.distinct()
val teachers = slotLessons.map { it.lesson.teacher }.distinct()
Log.d(
TAG,
"build: date=${day.date} start=$startMin firstEnd=$first.end " +
@@ -271,7 +272,7 @@ class TeacherScheduleRepositoryImpl(
start = LocalTime.of(startMin / 60, startMin % 60),
end = LocalTime.of(first.end / 60, first.end % 60),
lessons = slotLessons
.sortedBy { it.lessonIndex }
.sortedBy { it.lesson.lessonIndex }
.map { it.toDomain() }
)
}
@@ -280,16 +281,33 @@ class TeacherScheduleRepositoryImpl(
return TeacherSchedule(period = period, days = days)
}
private fun TeacherLessonEntity.toDomain(): Lesson = Lesson(
raw = raw,
room = room,
group = groupName,
subgroup = subgroup,
subject = subject,
type = type,
teacher = teacher
private fun TeacherLessonWithDepartment.toDomain(): Lesson = Lesson(
raw = lesson.raw,
room = lesson.room,
group = lesson.groupName,
subgroup = lesson.subgroup,
subject = lesson.subject,
type = lesson.type,
teacher = lesson.teacher,
department = departmentLabel(departmentId, departmentName)
)
// Названия, не выводимые акронимом из слов (например «топ-уровня в сфере…»)
private val DEPARTMENT_LABELS = mapOf(
"974" to "Топ ИИ"
)
private fun departmentLabel(departmentId: String, departmentName: String?): String {
DEPARTMENT_LABELS[departmentId]?.let { return it }
val name = departmentName ?: return "каф. $departmentId"
val words = name.removePrefix("Кафедра ").removePrefix("кафедра ")
.substringBefore(" (")
.split(" ")
.filter { it.isNotBlank() && it != "и" }
val acronym = words.joinToString("") { it.first().uppercase().toString() }
return acronym.ifBlank { "каф. $departmentId" }
}
private fun LocalTime.toMinutesOfDay(): Int = hour * 60 + minute
private fun now(): Long = System.currentTimeMillis()
@@ -26,4 +26,6 @@ data class Lesson(
/** Преподаватель **/
val teacher: String? = null,
/** Кафедра-источник (только в расписании преподавателей) **/
val department: String? = null,
)
@@ -70,6 +70,16 @@ internal fun LessonSlot(slotIndex: Int, slot: TimeSlot, showTeacher: Boolean = t
) {
var any = false
if (lesson.department != null) {
Text(
lesson.department,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = hPadding)
)
any = true
}
val meta = listOfNotNull(
if (lesson.type == LessonType.OTHER) {
null