V3 von Brett her + constraints update
This commit is contained in:
parent
afbced61e3
commit
23e042b90c
16 changed files with 323 additions and 78 deletions
|
@ -5,7 +5,7 @@ prefix = ""
|
||||||
debug = false
|
debug = false
|
||||||
|
|
||||||
[schedule]
|
[schedule]
|
||||||
reference = "2019-06-12"
|
reference = "2019-06-10"
|
||||||
|
|
||||||
[general]
|
[general]
|
||||||
wiki_url = "https://wiki.kif.rocks/w/index.php?title=KIF470:Arbeitskreise&action=raw"
|
wiki_url = "https://wiki.kif.rocks/w/index.php?title=KIF470:Arbeitskreise&action=raw"
|
||||||
|
|
|
@ -46,6 +46,7 @@ fun checkConstraints(
|
||||||
for (leader in schedule.workGroup.leader) {
|
for (leader in schedule.workGroup.leader) {
|
||||||
for (s in against) {
|
for (s in against) {
|
||||||
if (
|
if (
|
||||||
|
schedule != s &&
|
||||||
leader in s.workGroup.leader &&
|
leader in s.workGroup.leader &&
|
||||||
start <= s.getAbsoluteEndTime() &&
|
start <= s.getAbsoluteEndTime() &&
|
||||||
s.getAbsoluteStartTime() <= end
|
s.getAbsoluteStartTime() <= end
|
||||||
|
@ -59,7 +60,7 @@ fun checkConstraints(
|
||||||
when (type) {
|
when (type) {
|
||||||
ConstraintType.OnlyOnDay -> {
|
ConstraintType.OnlyOnDay -> {
|
||||||
val onlyOnDay = constraints.map { it.day == schedule.day }
|
val onlyOnDay = constraints.map { it.day == schedule.day }
|
||||||
if (!onlyOnDay.any()) {
|
if (onlyOnDay.none()) {
|
||||||
val dayList = constraints.mapNotNull { it.day }.distinct().sorted()
|
val dayList = constraints.mapNotNull { it.day }.distinct().sorted()
|
||||||
errors += ConstraintError("Work group requires days $dayList, but is on ${schedule.day}!")
|
errors += ConstraintError("Work group requires days $dayList, but is on ${schedule.day}!")
|
||||||
}
|
}
|
||||||
|
@ -77,11 +78,11 @@ fun checkConstraints(
|
||||||
for (it in constraints) {
|
for (it in constraints) {
|
||||||
if (it.time == null) continue
|
if (it.time == null) continue
|
||||||
if (it.day == null) {
|
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}!")
|
errors += ConstraintError("Work group requires before time ${it.time}, but is on ${schedule.time}!")
|
||||||
}
|
}
|
||||||
} else {
|
} 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}!")
|
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) {
|
for (it in constraints) {
|
||||||
if (it.time == null) continue
|
if (it.time == null) continue
|
||||||
if (it.day == null) {
|
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}!")
|
errors += ConstraintError("Work group requires after time ${it.time}, but is on ${schedule.time}!")
|
||||||
}
|
}
|
||||||
} else {
|
} 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}!")
|
errors += ConstraintError("Work group requires after time ${it.time} on day ${it.day}, but is on ${schedule.time}!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,37 @@ import com.soywiz.klock.DateFormat
|
||||||
import com.soywiz.klock.KlockLocale
|
import com.soywiz.klock.KlockLocale
|
||||||
import com.soywiz.klock.format
|
import com.soywiz.klock.format
|
||||||
import com.soywiz.klock.locale.german
|
import com.soywiz.klock.locale.german
|
||||||
|
import com.soywiz.klock.min
|
||||||
|
|
||||||
fun formatDateTime(unix: Long) =
|
fun formatDateTime(unix: Long) =
|
||||||
DateFormat("EEEE, d. MMMM y HH:mm")
|
DateFormat("EEEE, d. MMMM y HH:mm")
|
||||||
.withLocale(KlockLocale.german)
|
.withLocale(KlockLocale.german)
|
||||||
.format(unix)
|
.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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,11 @@ object ScheduleRepository : Repository<Schedule> {
|
||||||
return parser.parse(json, Schedule.serializer())
|
return parser.parse(json, Schedule.serializer())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getUpcoming(count: Int = 8): List<Schedule> {
|
||||||
|
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 {
|
override suspend fun create(model: Schedule): Long {
|
||||||
return repositoryPost("$prefix/api/schedules", Message.json.stringify(Schedule.serializer(), model))
|
return repositoryPost("$prefix/api/schedules", Message.json.stringify(Schedule.serializer(), model))
|
||||||
?: throw IllegalStateException("Cannot create model!")
|
?: throw IllegalStateException("Cannot create model!")
|
||||||
|
|
|
@ -1,38 +1,51 @@
|
||||||
package de.kif.frontend.views.board
|
package de.kif.frontend.views.board
|
||||||
|
|
||||||
import com.soywiz.klock.DateFormat
|
import com.soywiz.klock.DateFormat
|
||||||
|
import com.soywiz.klock.DateTimeTz
|
||||||
import com.soywiz.klock.KlockLocale
|
import com.soywiz.klock.KlockLocale
|
||||||
|
import com.soywiz.klock.format
|
||||||
import com.soywiz.klock.locale.german
|
import com.soywiz.klock.locale.german
|
||||||
|
import de.kif.frontend.views.overview.getByClassOrCreate
|
||||||
import de.westermann.kwebview.interval
|
import de.westermann.kwebview.interval
|
||||||
|
import de.westermann.kwebview.iterator
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.HTMLSpanElement
|
||||||
import org.w3c.dom.get
|
import org.w3c.dom.get
|
||||||
import kotlin.browser.document
|
import kotlin.browser.document
|
||||||
import kotlin.js.Date
|
import kotlin.js.Date
|
||||||
|
|
||||||
fun initBoard() {
|
fun initBoard() {
|
||||||
//val boardSchedules = document.getElementsByClassName("board-schedules")[0] as HTMLElement
|
val dateContainer = document.getElementsByClassName("board-header-date")[0] as HTMLElement
|
||||||
val dateView = document.getElementsByClassName("board-header-date")[0] as HTMLElement
|
|
||||||
//val referenceTime = boardSchedules.dataset["reference"]?.toLongOrNull() ?: 0L
|
|
||||||
|
|
||||||
/*
|
val timeView = dateContainer.getByClassOrCreate<HTMLSpanElement>("board-header-date-time")
|
||||||
|
val dateView = dateContainer.getByClassOrCreate<HTMLSpanElement>("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<BoardSchedule>()
|
val scheduleList = mutableListOf<BoardSchedule>()
|
||||||
|
|
||||||
for (bs in boardSchedules.getElementsByClassName("board-schedule").iterator()) {
|
for (bs in boardRunning.getElementsByClassName("board-schedule").iterator()) {
|
||||||
scheduleList += BoardSchedule(bs)
|
scheduleList += BoardSchedule(bs)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
interval(1000) {
|
interval(1000) {
|
||||||
|
val now = Date.now().toLong() + diff
|
||||||
|
|
||||||
val currentTime = Date().let {
|
val dt = DateTimeTz.fromUnixLocal(now)
|
||||||
it.getHours().toString().padStart(2, '0') + ":" + it.getMinutes().toString().padStart(2, '0')
|
|
||||||
}
|
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) }
|
scheduleList.forEach { it.updateTime(now) }
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,24 +1,36 @@
|
||||||
package de.kif.frontend.views.board
|
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.common.model.Schedule
|
||||||
import de.kif.frontend.views.overview.getByClassOrCreate
|
import de.kif.frontend.views.overview.getByClassOrCreate
|
||||||
import de.westermann.kwebview.View
|
import de.westermann.kwebview.View
|
||||||
|
import org.w3c.dom.HTMLDivElement
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
import org.w3c.dom.HTMLSpanElement
|
import org.w3c.dom.HTMLSpanElement
|
||||||
import org.w3c.dom.get
|
import org.w3c.dom.get
|
||||||
|
import kotlin.js.Date
|
||||||
|
|
||||||
class BoardSchedule(
|
class BoardSchedule(
|
||||||
view: HTMLElement
|
view: HTMLElement
|
||||||
) : View(view) {
|
) : View(view) {
|
||||||
private val roomView = view.getByClassOrCreate<HTMLSpanElement>("board-schedule-room")
|
private val colorView = view.getByClassOrCreate<HTMLDivElement>("board-schedule-color")
|
||||||
private val timeView = view.getByClassOrCreate<HTMLSpanElement>("board-schedule-time")
|
private val timeView = view.getByClassOrCreate<HTMLDivElement>("board-schedule-time")
|
||||||
private val colorView = view.getByClassOrCreate<HTMLSpanElement>("board-schedule-color")
|
private val nameView = view.getByClassOrCreate<HTMLDivElement>("board-schedule-name")
|
||||||
private val nameView = view.getByClassOrCreate<HTMLSpanElement>("board-schedule-name")
|
private val roomView = view.getByClassOrCreate<HTMLDivElement>("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) {
|
fun updateTime(now: Long) {
|
||||||
//console.log(time.toString(), now.toString())
|
timeView.textContent = if (startTime >= now) {
|
||||||
timeView.textContent = Schedule.timeDifferenceToString(time + now)
|
"Start in ${formatTimeDiff(startTime - now)}"
|
||||||
|
} else {
|
||||||
|
"Ende in ${formatTimeDiff(endTime - now)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
|
||||||
for ((s, l) in errors.map) {
|
for ((s, l) in errors.map) {
|
||||||
for (entry in body.calendarEntries) {
|
for (entry in body.calendarEntries) {
|
||||||
if (entry.scheduleId == s) {
|
if (entry.scheduleId == s) {
|
||||||
entry.error = l.isNotEmpty()
|
entry.setError(l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package de.kif.frontend.views.calendar
|
package de.kif.frontend.views.calendar
|
||||||
|
|
||||||
import de.kif.common.CALENDAR_GRID_WIDTH
|
import de.kif.common.CALENDAR_GRID_WIDTH
|
||||||
|
import de.kif.common.ConstraintError
|
||||||
import de.kif.common.model.Schedule
|
import de.kif.common.model.Schedule
|
||||||
import de.kif.common.model.WorkGroup
|
import de.kif.common.model.WorkGroup
|
||||||
import de.westermann.kwebview.iterator
|
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 length = dataset["length"]?.toIntOrNull() ?: 0
|
||||||
|
|
||||||
var pending by classList.property("pending")
|
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 var nextScroll = 0.0
|
||||||
|
|
||||||
private val editable: Boolean
|
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 calendarTools = if (editable) CalendarTools(this) else null
|
||||||
|
private val calendarErrors = if (editable) CalendarErrors() else null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
onMouseDown { event ->
|
onMouseDown { event ->
|
||||||
|
@ -207,6 +209,9 @@ class CalendarEntry(private val calendar: CalendarBody, view: HTMLElement) : Vie
|
||||||
calendarTools.update(s)
|
calendarTools.update(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (calendarErrors != null) {
|
||||||
|
html.appendChild(calendarErrors.html)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(schedule: Schedule) {
|
fun load(schedule: Schedule) {
|
||||||
|
@ -263,6 +268,11 @@ class CalendarEntry(private val calendar: CalendarBody, view: HTMLElement) : Vie
|
||||||
nameView.textContent = workGroup.name
|
nameView.textContent = workGroup.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setError(errors: List<ConstraintError>) {
|
||||||
|
error = errors.isNotEmpty()
|
||||||
|
calendarErrors?.setErrors(errors)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(calendar: CalendarBody, schedule: Schedule): CalendarEntry {
|
fun create(calendar: CalendarBody, schedule: Schedule): CalendarEntry {
|
||||||
val entry = CalendarEntry(calendar, createHtmlView())
|
val entry = CalendarEntry(calendar, createHtmlView())
|
||||||
|
|
|
@ -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<TextView>() {
|
||||||
|
|
||||||
|
fun setErrors(errors: List<ConstraintError>) {
|
||||||
|
clear()
|
||||||
|
|
||||||
|
for (error in errors) {
|
||||||
|
textView(error.reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
width: 30%;
|
width: 29%;
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-content {
|
.board-content {
|
||||||
top: 8rem !important;
|
top: 9rem !important;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
}
|
}
|
||||||
|
@ -59,15 +59,22 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-running {
|
.board-running {
|
||||||
column-count: 2;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-schedule {
|
.board-schedule {
|
||||||
line-height: 1.3rem;
|
line-height: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
min-width: 10rem;
|
min-width: 10rem;
|
||||||
display: flex;
|
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 {
|
.board-schedule-color {
|
||||||
|
@ -76,25 +83,33 @@
|
||||||
span {
|
span {
|
||||||
display: block;
|
display: block;
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
width: 0.8rem;
|
width: 0.5rem;
|
||||||
height: 0.8rem;
|
height: 1rem;
|
||||||
border-radius: 100%;
|
margin-top: 0.5rem;
|
||||||
margin-top: 0.1rem;
|
margin-left: 0.3rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-schedule-time {
|
.board-schedule-time {
|
||||||
width: 7rem;
|
width: 8rem;
|
||||||
color: var(--text-secondary-color)
|
color: var(--text-secondary-color);
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-schedule-name {
|
.board-schedule-name {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-schedule-room {
|
.board-schedule-room {
|
||||||
width: 4rem;
|
width: 8rem;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
color: var(--text-secondary-color)
|
color: var(--text-secondary-color);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-calendar {
|
.board-calendar {
|
||||||
|
@ -108,10 +123,12 @@
|
||||||
.calendar-table-box {
|
.calendar-table-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-row {
|
.calendar-row {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
height: 0.7rem !important;
|
height: 0.7rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-cell {
|
.calendar-cell {
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
@ -119,6 +136,7 @@
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-entry::after {
|
.calendar-entry::after {
|
||||||
content: none;
|
content: none;
|
||||||
}
|
}
|
||||||
|
@ -128,13 +146,25 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
top: 50%;
|
top: 0;
|
||||||
margin-top: -1rem;
|
|
||||||
left: 8rem;
|
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 {
|
.board-twitter {
|
||||||
& > * {
|
& > * {
|
||||||
margin-top: -1px !important;
|
margin-top: -1px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: var(--background-secondary-color);
|
background-color: var(--background-secondary-color);
|
||||||
z-index: 5;
|
z-index: 10;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
||||||
|
@ -106,6 +106,7 @@
|
||||||
&:hover {
|
&:hover {
|
||||||
.menu-content {
|
.menu-content {
|
||||||
display: block;
|
display: block;
|
||||||
|
border-bottom: solid 1px var(--table-border-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,6 +122,7 @@
|
||||||
position: static;
|
position: static;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
z-index: unset;
|
z-index: unset;
|
||||||
|
border-bottom: none !important;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
height: calc(33.3333% - 1rem);
|
height: calc(33.3333% - 1rem);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-bottom: solid 2px var(--input-border-color);
|
border-bottom: solid 2px var(--input-border-color);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
height: calc(33.3333% + 2rem);
|
height: calc(33.3333% + 2rem);
|
||||||
|
@ -50,6 +51,7 @@
|
||||||
|
|
||||||
.calendar-body {
|
.calendar-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: calc(100vw - 9.6rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-row {
|
.calendar-row {
|
||||||
|
@ -64,12 +66,27 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
line-height: 2rem;
|
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-header {
|
||||||
.calendar-cell {
|
.calendar-cell {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding: 0 0.2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,9 +94,34 @@
|
||||||
.wall-calendar {
|
.wall-calendar {
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
|
width: calc(100% - 2.4rem);
|
||||||
|
margin-left: 2.4rem;
|
||||||
|
|
||||||
.calendar-table {
|
.calendar-table {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0;
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import de.kif.backend.Configuration
|
||||||
import de.kif.backend.repository.RoomRepository
|
import de.kif.backend.repository.RoomRepository
|
||||||
import de.kif.backend.repository.ScheduleRepository
|
import de.kif.backend.repository.ScheduleRepository
|
||||||
import de.kif.backend.view.respondMain
|
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.Route
|
||||||
import io.ktor.routing.get
|
import io.ktor.routing.get
|
||||||
import kotlinx.css.CSSBuilder
|
import kotlinx.css.CSSBuilder
|
||||||
|
@ -16,20 +18,40 @@ import java.util.*
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
data class BoardSchedule(
|
||||||
|
val schedule: Schedule,
|
||||||
|
val startTime: Int,
|
||||||
|
val endTime: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun getUpcoming(limit: Int = 8): List<Schedule> {
|
||||||
|
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() {
|
fun Route.board() {
|
||||||
get("/brett") {
|
get("/brett") {
|
||||||
val scheduleList = ScheduleRepository.all().map {
|
val scheduleList = getUpcoming()
|
||||||
it to it.getAbsoluteStartTime() * 60
|
|
||||||
}.sortedBy { it.second }
|
|
||||||
|
|
||||||
val referenceTime = Configuration.Schedule.referenceDate.time / 1000
|
val now = Date()
|
||||||
val now = referenceTime - (Date().time / 1000)
|
val referenceTime = Configuration.Schedule.referenceDate.time
|
||||||
|
|
||||||
val refDate = Configuration.Schedule.referenceDate
|
val refDate = Configuration.Schedule.referenceDate
|
||||||
val todayDate = Date()
|
|
||||||
|
|
||||||
val refDay = refDate.time / (1000 * 60 * 60 * 24)
|
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 day = (todayDay - refDay).toInt()
|
||||||
|
|
||||||
val list = ScheduleRepository.getByDay(day)
|
val list = ScheduleRepository.getByDay(day)
|
||||||
|
@ -66,7 +88,7 @@ fun Route.board() {
|
||||||
div("board") {
|
div("board") {
|
||||||
div("board-header") {
|
div("board-header") {
|
||||||
div("board-running") {
|
div("board-running") {
|
||||||
for ((schedule, time) in scheduleList) {
|
for (schedule in scheduleList) {
|
||||||
div("board-schedule") {
|
div("board-schedule") {
|
||||||
attributes["data-id"] = schedule.id.toString()
|
attributes["data-id"] = schedule.id.toString()
|
||||||
|
|
||||||
|
@ -82,25 +104,17 @@ fun Route.board() {
|
||||||
}
|
}
|
||||||
|
|
||||||
div("board-schedule-time") {
|
div("board-schedule-time") {
|
||||||
attributes["data-time"] = time.toString()
|
val startTime = ((schedule.getAbsoluteStartTime() * 60 * 1000) + referenceTime)
|
||||||
attributes["data-duration"] = schedule.workGroup.length.toString()
|
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 (startTime >= now.time) {
|
||||||
if (it < 0) it + MINUTES_OF_DAY else it
|
+"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") {
|
div("board-schedule-name") {
|
||||||
|
@ -116,6 +130,8 @@ fun Route.board() {
|
||||||
div("board-logo") {
|
div("board-logo") {
|
||||||
img("KIF 47.0", "/static/images/logo.svg")
|
img("KIF 47.0", "/static/images/logo.svg")
|
||||||
div("board-header-date") {
|
div("board-header-date") {
|
||||||
|
attributes["data-now"] =
|
||||||
|
(now.time - 2 * 60 * 60 * 1000).toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package de.kif.backend.route
|
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.RoomRepository
|
||||||
import de.kif.backend.repository.ScheduleRepository
|
import de.kif.backend.repository.ScheduleRepository
|
||||||
import de.kif.backend.view.respondMain
|
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.Route
|
||||||
import io.ktor.routing.get
|
import io.ktor.routing.get
|
||||||
import kotlinx.html.div
|
import kotlinx.html.div
|
||||||
|
import kotlinx.html.span
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
data class WallData(
|
data class WallData(
|
||||||
val number: Int,
|
val number: Int,
|
||||||
val schedules: Map<Room, Map<Int, Schedule>>,
|
val schedules: Map<Room, Map<Int, Schedule>>,
|
||||||
val max: Int,
|
val max: Int?,
|
||||||
val min: Int
|
val min: Int?
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun genWallData(day: Int): WallData {
|
suspend fun genWallData(day: Int): WallData {
|
||||||
val list = ScheduleRepository.getByDay(day)
|
val list = ScheduleRepository.getByDay(day)
|
||||||
|
val rooms = RoomRepository.all()
|
||||||
|
|
||||||
|
if (list.isEmpty()) return WallData(day, rooms.associateWith { emptyMap<Int, Schedule>() }, null, null)
|
||||||
|
|
||||||
val schedules =
|
val schedules =
|
||||||
RoomRepository.all().associateWith { emptyMap<Int, Schedule>() } + list.groupBy { it.room }.mapValues { (_, it) ->
|
rooms.associateWith { emptyMap<Int, Schedule>() } + list.groupBy { it.room }.mapValues { (_, it) ->
|
||||||
it.associateBy {
|
it.associateBy {
|
||||||
it.time
|
it.time
|
||||||
}
|
}
|
||||||
|
@ -51,14 +59,32 @@ fun Route.wall() {
|
||||||
|
|
||||||
val days = (0..2).map { genWallData(it) }
|
val days = (0..2).map { genWallData(it) }
|
||||||
|
|
||||||
val min = days.map { it.min }.min() ?: days.first().min
|
var min = days.map { it.min }.filterNotNull().min() ?: 12 * 60
|
||||||
val max = days.map { it.max }.max() ?: days.first().max
|
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) {
|
respondMain(true, true) {
|
||||||
content {
|
content {
|
||||||
div("wall") {
|
div("wall") {
|
||||||
for (day in days) {
|
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-box") {
|
||||||
|
div("wall-name") {
|
||||||
|
span {
|
||||||
|
+dateString
|
||||||
|
}
|
||||||
|
}
|
||||||
div("wall-calendar calendar") {
|
div("wall-calendar calendar") {
|
||||||
renderCalendar(
|
renderCalendar(
|
||||||
CalendarOrientation.TIME_TO_ROOM,
|
CalendarOrientation.TIME_TO_ROOM,
|
||||||
|
|
|
@ -3,6 +3,7 @@ package de.kif.backend.route.api
|
||||||
import de.kif.backend.authenticate
|
import de.kif.backend.authenticate
|
||||||
import de.kif.backend.repository.RoomRepository
|
import de.kif.backend.repository.RoomRepository
|
||||||
import de.kif.backend.repository.ScheduleRepository
|
import de.kif.backend.repository.ScheduleRepository
|
||||||
|
import de.kif.backend.route.getUpcoming
|
||||||
import de.kif.common.model.Permission
|
import de.kif.common.model.Permission
|
||||||
import de.kif.common.model.Schedule
|
import de.kif.common.model.Schedule
|
||||||
import io.ktor.application.call
|
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") {
|
post("/api/schedules") {
|
||||||
try {
|
try {
|
||||||
authenticate(Permission.SCHEDULE) {
|
authenticate(Permission.SCHEDULE) {
|
||||||
|
|
Loading…
Reference in a new issue