From b856d31fcce99184ed43529eaaa5438a3b667bc8 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 11 Jul 2025 23:01:13 +0200 Subject: [PATCH] add date and turn entry into CRDT --- src/main/scala/fahrtenbuch/Main.scala | 8 ++++- .../fahrtenbuch/components/AppComponent.scala | 6 ++-- .../components/EntryComponent.scala | 35 ++++++++++--------- .../components/NewEntryInput.scala | 16 +++++---- src/main/scala/fahrtenbuch/model/Entry.scala | 15 ++++---- 5 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/main/scala/fahrtenbuch/Main.scala b/src/main/scala/fahrtenbuch/Main.scala index a0fada2..f74c61a 100644 --- a/src/main/scala/fahrtenbuch/Main.scala +++ b/src/main/scala/fahrtenbuch/Main.scala @@ -30,13 +30,19 @@ object Main { println("test") val allEntriesVar = Var(Set.empty[Entry]) + + // update entries whenever db updates entriesObservable.subscribe(entries => entries.onComplete { case Failure(exception) => println("failed to get entries from db") case Success(value) => allEntriesVar.set(value.toSet) } ) - val allEntries: Signal[List[Entry]] = allEntriesVar.signal.map(_.toList) + + // update db when edit events happen entryEditBus.stream.addObserver(entryDbObserver)(using unsafeWindowOwner) + val allEntries: Signal[Set[Entry]] = + allEntriesVar.signal + } diff --git a/src/main/scala/fahrtenbuch/components/AppComponent.scala b/src/main/scala/fahrtenbuch/components/AppComponent.scala index 4d6b149..5823a7a 100644 --- a/src/main/scala/fahrtenbuch/components/AppComponent.scala +++ b/src/main/scala/fahrtenbuch/components/AppComponent.scala @@ -5,7 +5,7 @@ import fahrtenbuch.model.Entry import fahrtenbuch.Main.entryEditBus import rdts.base.Uid -class AppComponent(allEntries: Signal[List[Entry]]): +class AppComponent(allEntries: Signal[Set[Entry]]): // tracks whenever a user clicks on an edit button val editClickBus = new EventBus[(Uid, Boolean)] @@ -21,7 +21,7 @@ class AppComponent(allEntries: Signal[List[Entry]]): .combineWith(editStateSignal) .map { case (entries, editState) => entries.toList - .sortBy(_.id) + .sortBy(_.date.payload.getTime()) .map(entry => EntryComponent( entry, @@ -42,7 +42,7 @@ class AppComponent(allEntries: Signal[List[Entry]]): cls := "table", thead( tr( -// th("Date"), + th("Datum"), th("Fahrer*in"), th("Start Km"), th("Ende Km"), diff --git a/src/main/scala/fahrtenbuch/components/EntryComponent.scala b/src/main/scala/fahrtenbuch/components/EntryComponent.scala index 5794c4c..a496a5b 100644 --- a/src/main/scala/fahrtenbuch/components/EntryComponent.scala +++ b/src/main/scala/fahrtenbuch/components/EntryComponent.scala @@ -14,18 +14,20 @@ class EntryComponent( ): def render: ReactiveHtmlElement[HTMLTableRowElement] = { if editMode then - val driverInput = input(cls := "input", value := entry.driver) + val driverInput = input(cls := "input", value := entry.driver.payload) val startKmInput = - input(cls := "input", value := entry.startKm.toString()) - val endKmInput = input(cls := "input", value := entry.endKm.toString()) - val animalInput = input(cls := "input", value := entry.animal) + input(cls := "input", value := entry.startKm.payload.toString()) + val endKmInput = + input(cls := "input", value := entry.endKm.payload.toString()) + val animalInput = input(cls := "input", value := entry.animal.payload) val costWearInput = input(cls := "input", value := entry.costWear.toString()) val costTotalInput = input(cls := "input", value := entry.costTotal.toString()) - val paidCheckbox = input(`type` := "checkbox", checked := entry.paid) + val paidCheckbox = + input(`type` := "checkbox", checked := entry.paid.payload) tr( -// td(input(cls := "input", value := entry.date.toDateString())), + td(), td(driverInput), td(startKmInput), td(endKmInput), @@ -40,10 +42,11 @@ class EntryComponent( editClickBus.emit(entry.id, false) entryEditBus.emit( entry.copy( - startKm = startKmInput.ref.value.toDouble, - endKm = endKmInput.ref.value.toDouble, - animal = animalInput.ref.value, - paid = paidCheckbox.ref.checked + startKm = + entry.startKm.write(startKmInput.ref.value.toDouble), + endKm = entry.endKm.write(endKmInput.ref.value.toDouble), + animal = entry.animal.write(animalInput.ref.value), + paid = entry.paid.write(paidCheckbox.ref.checked) ) ) }, @@ -56,14 +59,14 @@ class EntryComponent( ) else tr( - // td(entry.date.toDateString()), - td(entry.driver), - td(entry.startKm), - td(entry.endKm), - td(entry.animal), + td(entry.date.payload.toISOString()), + td(entry.driver.payload), + td(entry.startKm.payload), + td(entry.endKm.payload), + td(entry.animal.payload), td(s"${entry.costWear}€"), td(s"${entry.costTotal}€"), - td(if entry.paid then "Ja" else "Nein"), + td(if entry.paid.payload then "Ja" else "Nein"), td( button( cls := "button is-link", diff --git a/src/main/scala/fahrtenbuch/components/NewEntryInput.scala b/src/main/scala/fahrtenbuch/components/NewEntryInput.scala index 26b1e0a..51f897f 100644 --- a/src/main/scala/fahrtenbuch/components/NewEntryInput.scala +++ b/src/main/scala/fahrtenbuch/components/NewEntryInput.scala @@ -7,6 +7,8 @@ import com.raquo.laminar.api.features.unitArrows import fahrtenbuch.Main.entryEditBus import fahrtenbuch.model.Entry import rdts.base.Uid +import rdts.datatypes.LastWriterWins +import scala.scalajs.js.Date class NewEntryInput(showNewEntryField: Var[Boolean]): val newEntryDriver = input(cls := "input") @@ -17,6 +19,7 @@ class NewEntryInput(showNewEntryField: Var[Boolean]): def render = tr( + td(), td(newEntryDriver), td(newEntryStartKm), td(newEntryEndKm), @@ -29,13 +32,14 @@ class NewEntryInput(showNewEntryField: Var[Boolean]): cls := "button is-success", onClick --> { val id = Uid.gen() - val driver = newEntryDriver.ref.value - val startKm = newEntryStartKm.ref.value.toDouble - val endKm = newEntryEndKm.ref.value.toDouble - val animal = newEntryAnimal.ref.value - val paid = newEntryPaid.ref.checked + val driver = LastWriterWins.now(newEntryDriver.ref.value) + val startKm = LastWriterWins.now(newEntryStartKm.ref.value.toDouble) + val endKm = LastWriterWins.now(newEntryEndKm.ref.value.toDouble) + val animal = LastWriterWins.now(newEntryAnimal.ref.value) + val paid = LastWriterWins.now(newEntryPaid.ref.checked) + val date = LastWriterWins.now(new Date(Date.now())) entryEditBus.emit( - Entry(id, startKm, endKm, animal, paid, driver) + Entry(id, startKm, endKm, animal, paid, driver, date) ) showNewEntryField.set(false) newEntryDriver.ref.value = "" diff --git a/src/main/scala/fahrtenbuch/model/Entry.scala b/src/main/scala/fahrtenbuch/model/Entry.scala index ead3d27..499ef3e 100644 --- a/src/main/scala/fahrtenbuch/model/Entry.scala +++ b/src/main/scala/fahrtenbuch/model/Entry.scala @@ -5,17 +5,18 @@ import scala.scalajs.js.Date import org.getshaka.nativeconverter.NativeConverter import org.getshaka.nativeconverter.ParseState import scala.scalajs.js +import rdts.datatypes.LastWriterWins case class Entry( id: Uid, - startKm: Double, - endKm: Double, - animal: String, - paid: Boolean, - driver: String, - date: Option[Date] = None + startKm: LastWriterWins[Double], + endKm: LastWriterWins[Double], + animal: LastWriterWins[String], + paid: LastWriterWins[Boolean], + driver: LastWriterWins[String], + date: LastWriterWins[Date] ) derives NativeConverter: - val distance = endKm - startKm + val distance = endKm.payload - startKm.payload // 13 cent pro km, 5 cent Abnutzung def costGas: Double = distance * 0.13