From d791332b9ff57756693563fe30b4b8c3f3060aec Mon Sep 17 00:00:00 2001 From: Julian Date: Sat, 7 Jun 2025 19:51:31 +0200 Subject: [PATCH] main frontend functionality working --- build.sbt | 9 +- src/main/scala/fahrtenbuch/Entry.scala | 59 +++++++++---- src/main/scala/fahrtenbuch/main.scala | 110 +++++++++++++++++-------- 3 files changed, 122 insertions(+), 56 deletions(-) diff --git a/build.sbt b/build.sbt index b1ae84d..5e5d41e 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,7 @@ import org.scalajs.linker.interface.ModuleSplitStyle -lazy val fahrtenbuch = project.in(file(".")) +lazy val fahrtenbuch = project + .in(file(".")) .enablePlugins(ScalaJSPlugin) // Enable the Scala.js plugin in this project .settings( scalaVersion := "3.7.1", @@ -18,12 +19,14 @@ lazy val fahrtenbuch = project.in(file(".")) scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) .withModuleSplitStyle( - ModuleSplitStyle.SmallModulesFor(List("fahrtenbuch"))) + ModuleSplitStyle.SmallModulesFor(List("fahrtenbuch")) + ) }, /* Depend on the scalajs-dom library. * It provides static types for the browser DOM APIs. */ libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.8.0", - libraryDependencies += "com.raquo" %%% "laminar" % "17.2.1" + libraryDependencies += "com.raquo" %%% "laminar" % "17.2.1", + libraryDependencies += "de.tu-darmstadt.stg" %%% "rdts" % "0.37.0" ) diff --git a/src/main/scala/fahrtenbuch/Entry.scala b/src/main/scala/fahrtenbuch/Entry.scala index d635e1f..61068e5 100644 --- a/src/main/scala/fahrtenbuch/Entry.scala +++ b/src/main/scala/fahrtenbuch/Entry.scala @@ -1,11 +1,14 @@ package fahrtenbuch +import rdts.base.Uid import scala.scalajs.js.Date import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.api.features.unitArrows import org.scalajs.dom.HTMLTableRowElement import com.raquo.laminar.nodes.ReactiveHtmlElement - -type Id = String +import fahrtenbuch.Main.editClickBus +import scala.annotation.threadUnsafe +import fahrtenbuch.Main.entryEditBus case class EntryComponent( entry: Entry, @@ -13,18 +16,39 @@ case class EntryComponent( ): def render: ReactiveHtmlElement[HTMLTableRowElement] = { if editMode then + val driverInput = input(cls := "input", value := entry.driver) + 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) + 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) tr( - td(input(cls := "input")), - td(input(cls := "input")), - td(input(cls := "input")), - td(input(cls := "input")), - td(input(cls := "input")), - td(input(cls := "input")), - td(input(cls := "input")), - td(input(cls := "input")), +// td(input(cls := "input", value := entry.date.toDateString())), + td(driverInput), + td(startKmInput), + td(endKmInput), + td(animalInput), + td(), + td(), + td(paidCheckbox), td( button( cls := "button is-success", + onClick --> { + 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 + ) + ) + }, span( cls := "icon edit", i(cls := "mdi mdi-18px mdi-check-bold") @@ -34,17 +58,18 @@ case class EntryComponent( ) else tr( - td(entry.date.toDateString()), + // td(entry.date.toDateString()), td(entry.driver), td(entry.startKm), td(entry.endKm), td(entry.animal), - td(entry.costWear), - td(entry.costTotal), - td(entry.paid), + td(s"${entry.costWear}€"), + td(s"${entry.costTotal}€"), + td(if entry.paid then "Ja" else "Nein"), td( button( cls := "button is-link", + onClick --> editClickBus.emit(entry.id, true), span(cls := "icon edit", i(cls := "mdi mdi-18px mdi-pencil")) ) ) @@ -52,13 +77,13 @@ case class EntryComponent( } case class Entry( - id: Id, + id: Uid, startKm: Double, endKm: Double, - date: Date, animal: String, paid: Boolean, - driver: String + driver: String, + date: Option[Date] = None ): val distance = endKm - startKm diff --git a/src/main/scala/fahrtenbuch/main.scala b/src/main/scala/fahrtenbuch/main.scala index 22d503e..7ad5d6f 100644 --- a/src/main/scala/fahrtenbuch/main.scala +++ b/src/main/scala/fahrtenbuch/main.scala @@ -4,7 +4,9 @@ import scala.scalajs.js import scala.scalajs.js.annotation.* import org.scalajs.dom import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.api.features.unitArrows import scala.scalajs.js.Date +import rdts.base.Uid // import javascriptLogo from "/javascript.svg" //@js.native @JSImport("/javascript.svg", JSImport.Default) @@ -20,27 +22,75 @@ def Fahrtenbuch(): Unit = object Main { - val entries = Var( - List( - Entry("0", 100.0, 200.0, new Date(), "🐷", true, "Gesine"), - Entry("1", 200.0, 300.0, new Date(), "Dog", false, "Bob") - ) - ) - val entriesSignal = entries.signal + // tracks whenever a user clicks on an edit button + val editClickBus = new EventBus[(Uid, Boolean)] + val editStateSignal: Signal[Map[Uid, Boolean]] = + editClickBus.stream.foldLeft(Map.empty[Uid, Boolean]) { + case (acc, (id, value)) => + acc + (id -> value) + } - val editState = Var(Map.empty[Id, Boolean]) - val editStateSignal = editState.signal + // track changes to entries + val entryEditBus = new EventBus[Entry] + val allEntries = entryEditBus.stream.foldLeft(Map.empty[Uid, Entry]) { + case (acc, entry) => + acc + (entry.id -> entry) + } val entryComponents: Signal[List[EntryComponent]] = - entriesSignal + allEntries .combineWith(editStateSignal) .map { case (entries, editState) => - entries.map(entry => - EntryComponent(entry, editState.getOrElse(entry.id, false)) - ) + entries.values.toList + .sortBy(_.id) + .map(entry => + EntryComponent(entry, editState.getOrElse(entry.id, false)) + ) } - val editClickBus = new EventBus[(Id, Boolean)] + val showNewEntryField = Var(false) + + val newEntryInput = + val newEntryDriver = input(cls := "input") + val newEntryStartKm = input(cls := "input") + val newEntryEndKm = input(cls := "input") + val newEntryAnimal = input(cls := "input") + val newEntryPaid = input(`type` := "checkbox") + tr( + td(newEntryDriver), + td(newEntryStartKm), + td(newEntryEndKm), + td(newEntryAnimal), + td(), + td(), + td(newEntryPaid), + td( + button( + 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 + entryEditBus.emit( + Entry(id, startKm, endKm, animal, paid, driver) + ) + showNewEntryField.set(false) + newEntryDriver.ref.value = "" + newEntryStartKm.ref.value = "" + newEntryEndKm.ref.value = "" + newEntryAnimal.ref.value = "" + newEntryPaid.ref.checked = false + }, + span( + cls := "icon edit", + i(cls := "mdi mdi-18px mdi-check-bold") + ) + ) + ) + ) def appElement(): HtmlElement = div( @@ -50,7 +100,7 @@ object Main { cls := "table", thead( tr( - th("Date"), +// th("Date"), th("Fahrer*in"), th("Start Km"), th("Ende Km"), @@ -62,28 +112,16 @@ object Main { ) ), tbody( - children <-- entryComponents.map(_.map(_.render)) - ), - tr( - td(input(cls := "input")), - td(input(cls := "input")), - td(input(cls := "input")), - td(input(cls := "input")), - td(input(cls := "input")), - td(input(cls := "input")), - td(input(cls := "input")), - td(input(cls := "input")), - td( - button( - cls := "button is-success", - span( - cls := "icon edit", - i(cls := "mdi mdi-18px mdi-check-bold") - ) - ) - ) + children <-- entryComponents.map(_.map(_.render)), + child(newEntryInput) <-- showNewEntryField ) ), - button(cls := "button is-primary", "Eintrag hinzufügen") + button( + cls := "button is-primary", + onClick --> { _ => + showNewEntryField.set(true) + }, + "Eintrag hinzufügen" + ) ) }