diff --git a/portal.toml b/portal.toml index 3221a6f..caf34b3 100644 --- a/portal.toml +++ b/portal.toml @@ -5,7 +5,7 @@ prefix = "" debug = false [schedule] -reference = "2019-06-12" +reference = "2019-06-10" [general] wiki_url = "https://wiki.kif.rocks/w/index.php?title=KIF470:Arbeitskreise&action=raw" diff --git a/src/commonMain/kotlin/de/kif/common/ConstraintChecking.kt b/src/commonMain/kotlin/de/kif/common/ConstraintChecking.kt index d865d8c..7e9c7a8 100644 --- a/src/commonMain/kotlin/de/kif/common/ConstraintChecking.kt +++ b/src/commonMain/kotlin/de/kif/common/ConstraintChecking.kt @@ -46,6 +46,7 @@ fun checkConstraints( for (leader in schedule.workGroup.leader) { for (s in against) { if ( + schedule != s && leader in s.workGroup.leader && start <= s.getAbsoluteEndTime() && s.getAbsoluteStartTime() <= end @@ -59,7 +60,7 @@ fun checkConstraints( when (type) { ConstraintType.OnlyOnDay -> { val onlyOnDay = constraints.map { it.day == schedule.day } - if (!onlyOnDay.any()) { + if (onlyOnDay.none()) { val dayList = constraints.mapNotNull { it.day }.distinct().sorted() errors += ConstraintError("Work group requires days $dayList, but is on ${schedule.day}!") } @@ -77,11 +78,11 @@ fun checkConstraints( for (it in constraints) { if (it.time == null) continue if (it.day == null) { - if (it.time > schedule.time) { + if (it.time < schedule.time) { errors += ConstraintError("Work group requires before time ${it.time}, but is on ${schedule.time}!") } } else { - if (it.day == schedule.day && it.time > schedule.time) { + if (it.day == schedule.day && it.time < schedule.time) { errors += ConstraintError("Work group requires before time ${it.time} on day ${it.day}, but is on ${schedule.time}!") } } @@ -92,11 +93,11 @@ fun checkConstraints( for (it in constraints) { if (it.time == null) continue if (it.day == null) { - if (it.time < schedule.time) { + if (it.time > schedule.time) { errors += ConstraintError("Work group requires after time ${it.time}, but is on ${schedule.time}!") } } else { - if (it.day == schedule.day && it.time < schedule.time) { + if (it.day == schedule.day && it.time > schedule.time) { errors += ConstraintError("Work group requires after time ${it.time} on day ${it.day}, but is on ${schedule.time}!") } } diff --git a/src/commonMain/kotlin/de/kif/common/DateFormat.kt b/src/commonMain/kotlin/de/kif/common/DateFormat.kt index bea3c5b..15be4fd 100644 --- a/src/commonMain/kotlin/de/kif/common/DateFormat.kt +++ b/src/commonMain/kotlin/de/kif/common/DateFormat.kt @@ -4,8 +4,37 @@ import com.soywiz.klock.DateFormat import com.soywiz.klock.KlockLocale import com.soywiz.klock.format import com.soywiz.klock.locale.german +import com.soywiz.klock.min fun formatDateTime(unix: Long) = DateFormat("EEEE, d. MMMM y HH:mm") .withLocale(KlockLocale.german) .format(unix) + +fun formatTimeDiff(diff: Long): String { + var time = diff / 1000 + + val seconds = time % 60 + time /= 60 + val minutes = time % 60 + time /= 60 + val hours = time % 24 + time /= 24 + val days = time + + return when { + days > 0L -> { + if (days == 1L) "1 Tag" else "$days Tagen" + } + hours > 0L -> { + hours.toString().padStart(2, '0')+":"+ (minutes + if (seconds > 0) 1 else 0).toString().padStart(2,'0') + } + minutes > 0L -> { + "00:"+ (minutes+ if (seconds > 0) 1 else 0).toString().padStart(2,'0') + } + seconds > 0L -> { + "> 1 Minute" + } + else -> "vor $minutes Minuten" + } +} diff --git a/src/jsMain/kotlin/de/kif/frontend/repository/ScheduleRepository.kt b/src/jsMain/kotlin/de/kif/frontend/repository/ScheduleRepository.kt index aaaf1b3..2acc2a3 100644 --- a/src/jsMain/kotlin/de/kif/frontend/repository/ScheduleRepository.kt +++ b/src/jsMain/kotlin/de/kif/frontend/repository/ScheduleRepository.kt @@ -25,6 +25,11 @@ object ScheduleRepository : Repository { return parser.parse(json, Schedule.serializer()) } + suspend fun getUpcoming(count: Int = 8): List { + val json = repositoryGet("$prefix/api/schedule/upcoming?count=$count") ?: return emptyList() + return parser.parse(json, Schedule.serializer().list) + } + override suspend fun create(model: Schedule): Long { return repositoryPost("$prefix/api/schedules", Message.json.stringify(Schedule.serializer(), model)) ?: throw IllegalStateException("Cannot create model!") diff --git a/src/jsMain/kotlin/de/kif/frontend/views/board/Board.kt b/src/jsMain/kotlin/de/kif/frontend/views/board/Board.kt index f3cd636..ce97daa 100644 --- a/src/jsMain/kotlin/de/kif/frontend/views/board/Board.kt +++ b/src/jsMain/kotlin/de/kif/frontend/views/board/Board.kt @@ -1,38 +1,51 @@ package de.kif.frontend.views.board import com.soywiz.klock.DateFormat +import com.soywiz.klock.DateTimeTz import com.soywiz.klock.KlockLocale +import com.soywiz.klock.format import com.soywiz.klock.locale.german +import de.kif.frontend.views.overview.getByClassOrCreate import de.westermann.kwebview.interval +import de.westermann.kwebview.iterator import org.w3c.dom.HTMLElement +import org.w3c.dom.HTMLSpanElement import org.w3c.dom.get import kotlin.browser.document import kotlin.js.Date fun initBoard() { - //val boardSchedules = document.getElementsByClassName("board-schedules")[0] as HTMLElement - val dateView = document.getElementsByClassName("board-header-date")[0] as HTMLElement - //val referenceTime = boardSchedules.dataset["reference"]?.toLongOrNull() ?: 0L + val dateContainer = document.getElementsByClassName("board-header-date")[0] as HTMLElement - /* + val timeView = dateContainer.getByClassOrCreate("board-header-date-time") + val dateView = dateContainer.getByClassOrCreate("board-header-date-date") + + val initTime = Date.now().toLong() + val referenceInitTime = dateContainer.dataset["now"]?.toLongOrNull() ?: initTime + val diff = initTime - referenceInitTime + + + val boardRunning = document.getElementsByClassName("board-running")[0] as HTMLElement val scheduleList = mutableListOf() - for (bs in boardSchedules.getElementsByClassName("board-schedule").iterator()) { + for (bs in boardRunning.getElementsByClassName("board-schedule").iterator()) { scheduleList += BoardSchedule(bs) } - */ interval(1000) { + val now = Date.now().toLong() + diff - val currentTime = Date().let { - it.getHours().toString().padStart(2, '0') + ":" + it.getMinutes().toString().padStart(2, '0') - } + val dt = DateTimeTz.fromUnixLocal(now) + + timeView.textContent = DateFormat("HH:mm") + .withLocale(KlockLocale.german) + .format(dt) + + dateView.textContent = DateFormat("EEEE, d. MMMM y") + .withLocale(KlockLocale.german) + .format(dt) - dateView.innerText = currentTime - /* - val now = referenceTime - currentTime / 1000 scheduleList.forEach { it.updateTime(now) } - */ } -} \ No newline at end of file +} diff --git a/src/jsMain/kotlin/de/kif/frontend/views/board/BoardSchedule.kt b/src/jsMain/kotlin/de/kif/frontend/views/board/BoardSchedule.kt index 1d3b3ef..31aa3a9 100644 --- a/src/jsMain/kotlin/de/kif/frontend/views/board/BoardSchedule.kt +++ b/src/jsMain/kotlin/de/kif/frontend/views/board/BoardSchedule.kt @@ -1,24 +1,36 @@ package de.kif.frontend.views.board +import de.kif.common.formatDateTime +import de.kif.common.formatTimeDiff import de.kif.common.model.Schedule import de.kif.frontend.views.overview.getByClassOrCreate import de.westermann.kwebview.View +import org.w3c.dom.HTMLDivElement import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLSpanElement import org.w3c.dom.get +import kotlin.js.Date class BoardSchedule( view: HTMLElement ) : View(view) { - private val roomView = view.getByClassOrCreate("board-schedule-room") - private val timeView = view.getByClassOrCreate("board-schedule-time") - private val colorView = view.getByClassOrCreate("board-schedule-color") - private val nameView = view.getByClassOrCreate("board-schedule-name") + private val colorView = view.getByClassOrCreate("board-schedule-color") + private val timeView = view.getByClassOrCreate("board-schedule-time") + private val nameView = view.getByClassOrCreate("board-schedule-name") + private val roomView = view.getByClassOrCreate("board-schedule-room") - private var time: Long = timeView.dataset["time"]?.toLongOrNull() ?: 0L + private val schedule: Long = view.dataset["id"]?.toLongOrNull() ?: 0L + private val startTime: Long = timeView.dataset["startTime"]?.toLongOrNull() ?: 0L + private val endTime: Long = timeView.dataset["endTime"]?.toLongOrNull() ?: 0L fun updateTime(now: Long) { - //console.log(time.toString(), now.toString()) - timeView.textContent = Schedule.timeDifferenceToString(time + now) + timeView.textContent = if (startTime >= now) { + "Start in ${formatTimeDiff(startTime - now)}" + } else { + "Ende in ${formatTimeDiff(endTime - now)}" + } + } + + init { } } diff --git a/src/jsMain/kotlin/de/kif/frontend/views/calendar/Calendar.kt b/src/jsMain/kotlin/de/kif/frontend/views/calendar/Calendar.kt index b08d097..34485ed 100644 --- a/src/jsMain/kotlin/de/kif/frontend/views/calendar/Calendar.kt +++ b/src/jsMain/kotlin/de/kif/frontend/views/calendar/Calendar.kt @@ -64,7 +64,7 @@ class Calendar(calendar: HTMLElement) : View(calendar) { for ((s, l) in errors.map) { for (entry in body.calendarEntries) { if (entry.scheduleId == s) { - entry.error = l.isNotEmpty() + entry.setError(l) } } } diff --git a/src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarEntry.kt b/src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarEntry.kt index d169f0b..efa1447 100644 --- a/src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarEntry.kt +++ b/src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarEntry.kt @@ -1,6 +1,7 @@ package de.kif.frontend.views.calendar import de.kif.common.CALENDAR_GRID_WIDTH +import de.kif.common.ConstraintError import de.kif.common.model.Schedule import de.kif.common.model.WorkGroup import de.westermann.kwebview.iterator @@ -38,7 +39,7 @@ class CalendarEntry(private val calendar: CalendarBody, view: HTMLElement) : Vie var length = dataset["length"]?.toIntOrNull() ?: 0 var pending by classList.property("pending") - var error by classList.property("error") + private var error by classList.property("error") private var nextScroll = 0.0 private val editable: Boolean @@ -172,6 +173,7 @@ class CalendarEntry(private val calendar: CalendarBody, view: HTMLElement) : Vie } private val calendarTools = if (editable) CalendarTools(this) else null + private val calendarErrors = if (editable) CalendarErrors() else null init { onMouseDown { event -> @@ -207,6 +209,9 @@ class CalendarEntry(private val calendar: CalendarBody, view: HTMLElement) : Vie calendarTools.update(s) } } + if (calendarErrors != null) { + html.appendChild(calendarErrors.html) + } } fun load(schedule: Schedule) { @@ -263,6 +268,11 @@ class CalendarEntry(private val calendar: CalendarBody, view: HTMLElement) : Vie nameView.textContent = workGroup.name } + fun setError(errors: List) { + error = errors.isNotEmpty() + calendarErrors?.setErrors(errors) + } + companion object { fun create(calendar: CalendarBody, schedule: Schedule): CalendarEntry { val entry = CalendarEntry(calendar, createHtmlView()) diff --git a/src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarErrors.kt b/src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarErrors.kt new file mode 100644 index 0000000..e32b8ef --- /dev/null +++ b/src/jsMain/kotlin/de/kif/frontend/views/calendar/CalendarErrors.kt @@ -0,0 +1,21 @@ +package de.kif.frontend.views.calendar + +import de.kif.common.ConstraintError +import de.kif.common.model.Room +import de.kif.common.model.Schedule +import de.kif.frontend.launch +import de.kif.frontend.repository.ScheduleRepository +import de.westermann.kwebview.View +import de.westermann.kwebview.ViewCollection +import de.westermann.kwebview.components.* + +class CalendarErrors() : ViewCollection() { + + fun setErrors(errors: List) { + clear() + + for (error in errors) { + textView(error.reason) + } + } +} \ No newline at end of file diff --git a/src/jsMain/resources/style/components/_board.scss b/src/jsMain/resources/style/components/_board.scss index 716faf3..773207c 100644 --- a/src/jsMain/resources/style/components/_board.scss +++ b/src/jsMain/resources/style/components/_board.scss @@ -25,7 +25,7 @@ } &:last-child { - width: 30%; + width: 29%; float: right; } } @@ -41,7 +41,7 @@ } .board-content { - top: 8rem !important; + top: 9rem !important; bottom: 0; height: auto !important; } @@ -59,15 +59,22 @@ } .board-running { - column-count: 2; + display: flex; + flex-wrap: wrap; } .board-schedule { - line-height: 1.3rem; + line-height: 2rem; height: 2rem; min-width: 10rem; display: flex; - border-bottom: solid 1px var(--table-border-color) + border-bottom: solid 1px var(--table-border-color); + width: calc(50% - 1rem); + margin-left: 1rem; + + &:nth-last-child(1), &:nth-last-child(2) { + border-bottom: none; + } } .board-schedule-color { @@ -76,25 +83,33 @@ span { display: block; background-color: var(--primary-color); - width: 0.8rem; - height: 0.8rem; - border-radius: 100%; - margin-top: 0.1rem; + width: 0.5rem; + height: 1rem; + margin-top: 0.5rem; + margin-left: 0.3rem; } } .board-schedule-time { - width: 7rem; - color: var(--text-secondary-color) + width: 8rem; + color: var(--text-secondary-color); + text-align: center; + overflow: hidden; + text-overflow: ellipsis; } .board-schedule-name { + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; } .board-schedule-room { - width: 4rem; + width: 8rem; text-align: right; - color: var(--text-secondary-color) + color: var(--text-secondary-color); + overflow: hidden; + text-overflow: ellipsis; } .board-calendar { @@ -108,10 +123,12 @@ .calendar-table-box { width: 100%; } + .calendar-row { width: 100% !important; height: 0.7rem !important; } + .calendar-cell { &:not(:first-child) { flex-grow: 1; @@ -119,6 +136,7 @@ flex-basis: 0; } } + .calendar-entry::after { content: none; } @@ -128,13 +146,25 @@ position: absolute; line-height: 2rem; font-size: 2rem; - top: 50%; - margin-top: -1rem; + top: 0; left: 8rem; + padding: 1.8rem 0; + + span { + display: block; + } + + .board-header-date-time { + font-size: 2rem; + } + + .board-header-date-date { + font-size: 1.2rem; + } } .board-twitter { - & > * { - margin-top: -1px !important; - } + & > * { + margin-top: -1px !important; + } } diff --git a/src/jsMain/resources/style/components/_calendar.scss b/src/jsMain/resources/style/components/_calendar.scss index df3f962..d3b7775 100644 --- a/src/jsMain/resources/style/components/_calendar.scss +++ b/src/jsMain/resources/style/components/_calendar.scss @@ -611,3 +611,30 @@ } } } + +.calendar-errors { + position: absolute; + top: 100%; + margin-top: 0.2rem; + left: 0; + background-color: var(--background-primary-color); + color: var(--text-secondary-color); + border-radius: $border-radius; + display: none; + z-index: 10; + width: max-content; + + border: solid 1px var(--input-border-color); + box-shadow: 0 0.1rem 0.2rem var(--shadow-color); + + span { + display: block; + padding: 0 0.5rem; + } +} + +.calendar-entry.error { + .calendar-errors { + display: block; + } +} diff --git a/src/jsMain/resources/style/components/_menu.scss b/src/jsMain/resources/style/components/_menu.scss index 6f65816..8fcbcce 100644 --- a/src/jsMain/resources/style/components/_menu.scss +++ b/src/jsMain/resources/style/components/_menu.scss @@ -75,7 +75,7 @@ display: none; position: absolute; background-color: var(--background-secondary-color); - z-index: 5; + z-index: 10; left: 0; right: 0; @@ -106,6 +106,7 @@ &:hover { .menu-content { display: block; + border-bottom: solid 1px var(--table-border-color); } } } @@ -121,6 +122,7 @@ position: static; background-color: transparent; z-index: unset; + border-bottom: none !important; a { display: inline-block; diff --git a/src/jsMain/resources/style/components/_wall.scss b/src/jsMain/resources/style/components/_wall.scss index b3f4af2..0743bfb 100644 --- a/src/jsMain/resources/style/components/_wall.scss +++ b/src/jsMain/resources/style/components/_wall.scss @@ -14,6 +14,7 @@ height: calc(33.3333% - 1rem); overflow: hidden; border-bottom: solid 2px var(--input-border-color); + position: relative; &:first-child { height: calc(33.3333% + 2rem); @@ -50,6 +51,7 @@ .calendar-body { display: flex; + width: calc(100vw - 9.6rem); } .calendar-row { @@ -64,12 +66,27 @@ flex-grow: 1; flex-shrink: 1; line-height: 2rem; - width: 100%; + width: 100% !important; + + &:first-child { + padding-left: 0 !important; + span { + padding-left: 0.2rem; + position: absolute; + top: 0; + left: 0; + } + } } .calendar-header { .calendar-cell { overflow: hidden; + text-overflow: ellipsis; + + span { + padding: 0 0.2rem; + } } } } @@ -77,9 +94,34 @@ .wall-calendar { margin-top: 0 !important; height: 100% !important; + width: calc(100% - 2.4rem); + margin-left: 2.4rem; .calendar-table { overflow: hidden; padding: 0; } +} + +.wall-name { + position: absolute; + left: 0; + top: 0; + width: 2.4rem; + height: 100%; + border-right: solid 1px var(--table-border-color); + + span { + display: block; + position: absolute; + transform: rotate(-90deg); + width: 10rem; + line-height: 2.4rem; + text-align: center; + transform-origin: center; + left: 50%; + top: 50%; + margin-left: -5rem; + margin-top: -1.2rem; + } } \ No newline at end of file diff --git a/src/jvmMain/kotlin/de/kif/backend/route/Board.kt b/src/jvmMain/kotlin/de/kif/backend/route/Board.kt index d39fe38..ff31923 100644 --- a/src/jvmMain/kotlin/de/kif/backend/route/Board.kt +++ b/src/jvmMain/kotlin/de/kif/backend/route/Board.kt @@ -4,6 +4,8 @@ import de.kif.backend.Configuration import de.kif.backend.repository.RoomRepository import de.kif.backend.repository.ScheduleRepository import de.kif.backend.view.respondMain +import de.kif.common.formatTimeDiff +import de.kif.common.model.Schedule import io.ktor.routing.Route import io.ktor.routing.get import kotlinx.css.CSSBuilder @@ -16,20 +18,40 @@ import java.util.* import kotlin.math.max import kotlin.math.min +data class BoardSchedule( + val schedule: Schedule, + val startTime: Int, + val endTime: Int +) + +suspend fun getUpcoming(limit: Int = 8): List { + val now = (Date().time / (1000 * 60)).toInt() - (Configuration.Schedule.referenceDate.time / (1000 * 60)) + return ScheduleRepository.all().asSequence() + .map { + BoardSchedule( + it, + it.getAbsoluteStartTime(), + it.getAbsoluteEndTime() + ) + } + .filter { it.endTime > now } + .sortedBy { it.startTime } + .take(limit) + .map { it.schedule } + .toList() +} + fun Route.board() { get("/brett") { - val scheduleList = ScheduleRepository.all().map { - it to it.getAbsoluteStartTime() * 60 - }.sortedBy { it.second } + val scheduleList = getUpcoming() - val referenceTime = Configuration.Schedule.referenceDate.time / 1000 - val now = referenceTime - (Date().time / 1000) + val now = Date() + val referenceTime = Configuration.Schedule.referenceDate.time val refDate = Configuration.Schedule.referenceDate - val todayDate = Date() val refDay = refDate.time / (1000 * 60 * 60 * 24) - val todayDay = todayDate.time / (1000 * 60 * 60 * 24) + val todayDay = now.time / (1000 * 60 * 60 * 24) val day = (todayDay - refDay).toInt() val list = ScheduleRepository.getByDay(day) @@ -66,7 +88,7 @@ fun Route.board() { div("board") { div("board-header") { div("board-running") { - for ((schedule, time) in scheduleList) { + for (schedule in scheduleList) { div("board-schedule") { attributes["data-id"] = schedule.id.toString() @@ -82,25 +104,17 @@ fun Route.board() { } div("board-schedule-time") { - attributes["data-time"] = time.toString() - attributes["data-duration"] = schedule.workGroup.length.toString() + val startTime = ((schedule.getAbsoluteStartTime() * 60 * 1000) + referenceTime) + val endTime = ((schedule.getAbsoluteEndTime() * 60 * 1000) + referenceTime) + attributes["data-start-time"] = startTime.toString() + attributes["data-end-time"] = endTime.toString() - val startTime = (time % MINUTES_OF_DAY).let { - if (it < 0) it + MINUTES_OF_DAY else it + if (startTime >= now.time) { + +"Start in ${formatTimeDiff(startTime - now.time)}" + } else { + +"Ende in ${formatTimeDiff(endTime - now.time)}" } - val sm = (startTime % 60).toString().padStart(2, '0') - val sh = (startTime / 60).toString().padStart(2, '0') - val startTimeString = "$sh:$sm" - - val endTime = ((time + schedule.workGroup.length) % MINUTES_OF_DAY).let { - if (it < 0) it + MINUTES_OF_DAY else it - } - val em = (endTime % 60).toString().padStart(2, '0') - val eh = (endTime / 60).toString().padStart(2, '0') - val endTimeString = "$eh:$em" - - +"$startTimeString - $endTimeString" } div("board-schedule-name") { @@ -116,6 +130,8 @@ fun Route.board() { div("board-logo") { img("KIF 47.0", "/static/images/logo.svg") div("board-header-date") { + attributes["data-now"] = + (now.time - 2 * 60 * 60 * 1000).toString() } } } diff --git a/src/jvmMain/kotlin/de/kif/backend/route/Wall.kt b/src/jvmMain/kotlin/de/kif/backend/route/Wall.kt index fe58b3c..7c51b09 100644 --- a/src/jvmMain/kotlin/de/kif/backend/route/Wall.kt +++ b/src/jvmMain/kotlin/de/kif/backend/route/Wall.kt @@ -1,5 +1,8 @@ package de.kif.backend.route +import com.soywiz.klock.* +import com.soywiz.klock.locale.german +import de.kif.backend.Configuration import de.kif.backend.repository.RoomRepository import de.kif.backend.repository.ScheduleRepository import de.kif.backend.view.respondMain @@ -8,20 +11,25 @@ import de.kif.common.model.Schedule import io.ktor.routing.Route import io.ktor.routing.get import kotlinx.html.div +import kotlinx.html.span import kotlin.math.max import kotlin.math.min data class WallData( val number: Int, val schedules: Map>, - val max: Int, - val min: Int + val max: Int?, + val min: Int? ) suspend fun genWallData(day: Int): WallData { val list = ScheduleRepository.getByDay(day) + val rooms = RoomRepository.all() + + if (list.isEmpty()) return WallData(day, rooms.associateWith { emptyMap() }, null, null) + val schedules = - RoomRepository.all().associateWith { emptyMap() } + list.groupBy { it.room }.mapValues { (_, it) -> + rooms.associateWith { emptyMap() } + list.groupBy { it.room }.mapValues { (_, it) -> it.associateBy { it.time } @@ -51,14 +59,32 @@ fun Route.wall() { val days = (0..2).map { genWallData(it) } - val min = days.map { it.min }.min() ?: days.first().min - val max = days.map { it.max }.max() ?: days.first().max + var min = days.map { it.min }.filterNotNull().min() ?: 12 * 60 + val max = days.map { it.max }.filterNotNull().max() ?: 12 * 60 + + if (min > max) { + min = max + } + + val refDate = DateTime(Configuration.Schedule.referenceDate.time) respondMain(true, true) { content { div("wall") { for (day in days) { + + val date = refDate + day.number.days + val dateString = DateFormat("EEEE, d. MMMM") + .withLocale(KlockLocale.german) + .format(date) + + div("wall-box") { + div("wall-name") { + span { + +dateString + } + } div("wall-calendar calendar") { renderCalendar( CalendarOrientation.TIME_TO_ROOM, diff --git a/src/jvmMain/kotlin/de/kif/backend/route/api/Schedule.kt b/src/jvmMain/kotlin/de/kif/backend/route/api/Schedule.kt index 63df478..861b108 100644 --- a/src/jvmMain/kotlin/de/kif/backend/route/api/Schedule.kt +++ b/src/jvmMain/kotlin/de/kif/backend/route/api/Schedule.kt @@ -3,6 +3,7 @@ package de.kif.backend.route.api import de.kif.backend.authenticate import de.kif.backend.repository.RoomRepository import de.kif.backend.repository.ScheduleRepository +import de.kif.backend.route.getUpcoming import de.kif.common.model.Permission import de.kif.common.model.Schedule import io.ktor.application.call @@ -24,6 +25,16 @@ fun Route.scheduleApi() { } } + get("/api/schedules/upcoming") { + try { + val count = call.parameters["count"]?.toIntOrNull() ?: 8 + val schedules = getUpcoming(count) + call.success(schedules) + } catch (_: Exception) { + call.error(HttpStatusCode.InternalServerError) + } + } + post("/api/schedules") { try { authenticate(Permission.SCHEDULE) {