From 263a609084f9cfd528602bb5b927effce120faa9 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 11 Jul 2025 23:41:55 +0200 Subject: [PATCH] vibecode some validation logic --- .../components/EntryComponent.scala | 55 ++++++++++++++++++- .../components/NewEntryInput.scala | 50 ++++++++++++++++- 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/src/main/scala/fahrtenbuch/components/EntryComponent.scala b/src/main/scala/fahrtenbuch/components/EntryComponent.scala index a496a5b..27dbbd2 100644 --- a/src/main/scala/fahrtenbuch/components/EntryComponent.scala +++ b/src/main/scala/fahrtenbuch/components/EntryComponent.scala @@ -5,6 +5,7 @@ import org.scalajs.dom.HTMLTableRowElement import com.raquo.laminar.api.L.* import com.raquo.laminar.api.features.unitArrows import rdts.base.Uid +import scala.util.Try class EntryComponent( entry: Entry, @@ -15,10 +16,60 @@ class EntryComponent( def render: ReactiveHtmlElement[HTMLTableRowElement] = { if editMode then val driverInput = input(cls := "input", value := entry.driver.payload) + val startKmInput = - input(cls := "input", value := entry.startKm.payload.toString()) + input( + `type` := "number", + value := entry.startKm.payload.toString() + ) val endKmInput = - input(cls := "input", value := entry.endKm.payload.toString()) + input( + `type` := "number", + value := entry.endKm.payload.toString() + ) + + // Validation signals + val startKmValue = startKmInput + .events(onInput) + .mapTo(startKmInput.ref.value) + .startWith(entry.startKm.payload.toString()) + val endKmValue = endKmInput + .events(onInput) + .mapTo(endKmInput.ref.value) + .startWith(entry.endKm.payload.toString()) + + val startKmValid = + startKmValue.map(value => Try(value.toDouble).isSuccess) + val endKmValid = endKmValue.map(value => Try(value.toDouble).isSuccess) + + val rangeValid = startKmValue.combineWithFn(endKmValue) { (start, end) => + Try(start.toDouble).toOption.zip(Try(end.toDouble).toOption) match { + case Some((startVal, endVal)) => endVal > startVal + case None => true // Don't show range error if values are invalid + } + } + + val startKmError = startKmValid.combineWithFn(rangeValid) { + (valid, range) => + !valid || !range + } + + val endKmError = endKmValid.combineWithFn(rangeValid) { (valid, range) => + !valid || !range + } + + startKmInput.amend( + cls <-- startKmError.map(error => + if error then "input is-danger" else "input" + ) + ) + + endKmInput.amend( + cls <-- endKmError.map(error => + if error then "input is-danger" else "input" + ) + ) + val animalInput = input(cls := "input", value := entry.animal.payload) val costWearInput = input(cls := "input", value := entry.costWear.toString()) diff --git a/src/main/scala/fahrtenbuch/components/NewEntryInput.scala b/src/main/scala/fahrtenbuch/components/NewEntryInput.scala index 51f897f..ccfd91c 100644 --- a/src/main/scala/fahrtenbuch/components/NewEntryInput.scala +++ b/src/main/scala/fahrtenbuch/components/NewEntryInput.scala @@ -9,14 +9,60 @@ import fahrtenbuch.model.Entry import rdts.base.Uid import rdts.datatypes.LastWriterWins import scala.scalajs.js.Date +import scala.util.Try class NewEntryInput(showNewEntryField: Var[Boolean]): val newEntryDriver = input(cls := "input") - val newEntryStartKm = input(cls := "input") - val newEntryEndKm = input(cls := "input") + val newEntryStartKm = input(`type` := "number") + val newEntryEndKm = input(`type` := "number") val newEntryAnimal = input(cls := "input") val newEntryPaid = input(`type` := "checkbox") + // Validation signals + val startKmValue = newEntryStartKm + .events(onInput) + .mapTo(newEntryStartKm.ref.value) + .startWith("") + val endKmValue = newEntryEndKm + .events(onInput) + .mapTo(newEntryEndKm.ref.value) + .startWith("") + + val startKmValid = + startKmValue.map(value => value.isEmpty || Try(value.toDouble).isSuccess) + val endKmValid = + endKmValue.map(value => value.isEmpty || Try(value.toDouble).isSuccess) + + val rangeValid = startKmValue.combineWithFn(endKmValue) { (start, end) => + if (start.isEmpty || end.isEmpty) true + else { + Try(start.toDouble).toOption.zip(Try(end.toDouble).toOption) match { + case Some((startVal, endVal)) => endVal > startVal + case None => true // Don't show range error if values are invalid + } + } + } + + val startKmError = startKmValid.combineWithFn(rangeValid) { (valid, range) => + !valid || !range + } + + val endKmError = endKmValid.combineWithFn(rangeValid) { (valid, range) => + !valid || !range + } + + newEntryStartKm.amend( + cls <-- startKmError.map(error => + if error then "input is-danger" else "input" + ) + ) + + newEntryEndKm.amend( + cls <-- endKmError.map(error => + if error then "input is-danger" else "input" + ) + ) + def render = tr( td(),