main frontend functionality working

This commit is contained in:
Julian 2025-06-07 19:51:31 +02:00
parent 8377743d1b
commit d791332b9f
3 changed files with 122 additions and 56 deletions

View file

@ -1,6 +1,7 @@
import org.scalajs.linker.interface.ModuleSplitStyle 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 .enablePlugins(ScalaJSPlugin) // Enable the Scala.js plugin in this project
.settings( .settings(
scalaVersion := "3.7.1", scalaVersion := "3.7.1",
@ -18,12 +19,14 @@ lazy val fahrtenbuch = project.in(file("."))
scalaJSLinkerConfig ~= { scalaJSLinkerConfig ~= {
_.withModuleKind(ModuleKind.ESModule) _.withModuleKind(ModuleKind.ESModule)
.withModuleSplitStyle( .withModuleSplitStyle(
ModuleSplitStyle.SmallModulesFor(List("fahrtenbuch"))) ModuleSplitStyle.SmallModulesFor(List("fahrtenbuch"))
)
}, },
/* Depend on the scalajs-dom library. /* Depend on the scalajs-dom library.
* It provides static types for the browser DOM APIs. * It provides static types for the browser DOM APIs.
*/ */
libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.8.0", 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"
) )

View file

@ -1,11 +1,14 @@
package fahrtenbuch package fahrtenbuch
import rdts.base.Uid
import scala.scalajs.js.Date import scala.scalajs.js.Date
import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.api.L.{*, given}
import com.raquo.laminar.api.features.unitArrows
import org.scalajs.dom.HTMLTableRowElement import org.scalajs.dom.HTMLTableRowElement
import com.raquo.laminar.nodes.ReactiveHtmlElement import com.raquo.laminar.nodes.ReactiveHtmlElement
import fahrtenbuch.Main.editClickBus
type Id = String import scala.annotation.threadUnsafe
import fahrtenbuch.Main.entryEditBus
case class EntryComponent( case class EntryComponent(
entry: Entry, entry: Entry,
@ -13,18 +16,39 @@ case class EntryComponent(
): ):
def render: ReactiveHtmlElement[HTMLTableRowElement] = { def render: ReactiveHtmlElement[HTMLTableRowElement] = {
if editMode then 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( tr(
td(input(cls := "input")), // td(input(cls := "input", value := entry.date.toDateString())),
td(input(cls := "input")), td(driverInput),
td(input(cls := "input")), td(startKmInput),
td(input(cls := "input")), td(endKmInput),
td(input(cls := "input")), td(animalInput),
td(input(cls := "input")), td(),
td(input(cls := "input")), td(),
td(input(cls := "input")), td(paidCheckbox),
td( td(
button( button(
cls := "button is-success", 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( span(
cls := "icon edit", cls := "icon edit",
i(cls := "mdi mdi-18px mdi-check-bold") i(cls := "mdi mdi-18px mdi-check-bold")
@ -34,17 +58,18 @@ case class EntryComponent(
) )
else else
tr( tr(
td(entry.date.toDateString()), // td(entry.date.toDateString()),
td(entry.driver), td(entry.driver),
td(entry.startKm), td(entry.startKm),
td(entry.endKm), td(entry.endKm),
td(entry.animal), td(entry.animal),
td(entry.costWear), td(s"${entry.costWear}"),
td(entry.costTotal), td(s"${entry.costTotal}"),
td(entry.paid), td(if entry.paid then "Ja" else "Nein"),
td( td(
button( button(
cls := "button is-link", cls := "button is-link",
onClick --> editClickBus.emit(entry.id, true),
span(cls := "icon edit", i(cls := "mdi mdi-18px mdi-pencil")) span(cls := "icon edit", i(cls := "mdi mdi-18px mdi-pencil"))
) )
) )
@ -52,13 +77,13 @@ case class EntryComponent(
} }
case class Entry( case class Entry(
id: Id, id: Uid,
startKm: Double, startKm: Double,
endKm: Double, endKm: Double,
date: Date,
animal: String, animal: String,
paid: Boolean, paid: Boolean,
driver: String driver: String,
date: Option[Date] = None
): ):
val distance = endKm - startKm val distance = endKm - startKm

View file

@ -4,7 +4,9 @@ import scala.scalajs.js
import scala.scalajs.js.annotation.* import scala.scalajs.js.annotation.*
import org.scalajs.dom import org.scalajs.dom
import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.api.L.{*, given}
import com.raquo.laminar.api.features.unitArrows
import scala.scalajs.js.Date import scala.scalajs.js.Date
import rdts.base.Uid
// import javascriptLogo from "/javascript.svg" // import javascriptLogo from "/javascript.svg"
//@js.native @JSImport("/javascript.svg", JSImport.Default) //@js.native @JSImport("/javascript.svg", JSImport.Default)
@ -20,27 +22,75 @@ def Fahrtenbuch(): Unit =
object Main { object Main {
val entries = Var( // tracks whenever a user clicks on an edit button
List( val editClickBus = new EventBus[(Uid, Boolean)]
Entry("0", 100.0, 200.0, new Date(), "🐷", true, "Gesine"), val editStateSignal: Signal[Map[Uid, Boolean]] =
Entry("1", 200.0, 300.0, new Date(), "Dog", false, "Bob") editClickBus.stream.foldLeft(Map.empty[Uid, Boolean]) {
) case (acc, (id, value)) =>
) acc + (id -> value)
val entriesSignal = entries.signal }
val editState = Var(Map.empty[Id, Boolean]) // track changes to entries
val editStateSignal = editState.signal 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]] = val entryComponents: Signal[List[EntryComponent]] =
entriesSignal allEntries
.combineWith(editStateSignal) .combineWith(editStateSignal)
.map { case (entries, editState) => .map { case (entries, editState) =>
entries.map(entry => entries.values.toList
EntryComponent(entry, editState.getOrElse(entry.id, false)) .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 = def appElement(): HtmlElement =
div( div(
@ -50,7 +100,7 @@ object Main {
cls := "table", cls := "table",
thead( thead(
tr( tr(
th("Date"), // th("Date"),
th("Fahrer*in"), th("Fahrer*in"),
th("Start Km"), th("Start Km"),
th("Ende Km"), th("Ende Km"),
@ -62,28 +112,16 @@ object Main {
) )
), ),
tbody( tbody(
children <-- entryComponents.map(_.map(_.render)) children <-- entryComponents.map(_.map(_.render)),
), child(newEntryInput) <-- showNewEntryField
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")
)
)
)
) )
), ),
button(cls := "button is-primary", "Eintrag hinzufügen") button(
cls := "button is-primary",
onClick --> { _ =>
showNewEntryField.set(true)
},
"Eintrag hinzufügen"
)
) )
} }