(fix) Umbau zu SCS

This commit is contained in:
stefan
2025-07-21 12:08:20 +02:00
parent 83d0d81193
commit 62b5e71427
34 changed files with 3403 additions and 20 deletions
+11
View File
@@ -39,11 +39,22 @@ kotlin {
jsMain.dependencies {
// Kotlin React dependencies with explicit stable versions
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:18.2.0-pre.467")
implementation("org.jetbrains.kotlin-wrappers:kotlin-react-dom:18.2.0-pre.467")
implementation("org.jetbrains.kotlin-wrappers:kotlin-emotion:11.10.5-pre.467")
// Ktor client dependencies for API calls
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.js)
implementation(libs.ktor.client.contentNegotiation)
implementation(libs.ktor.client.serializationKotlinxJson)
// Coroutines for async operations
implementation(libs.kotlinx.coroutines.core)
// NPM dependencies
implementation(npm("react", "18.2.0"))
implementation(npm("react-dom", "18.2.0"))
implementation(npm("@r2wc/react-to-web-component", "2.0.4"))
}
}
}
+24
View File
@@ -0,0 +1,24 @@
import at.mocode.horses.ui.components.PferdeListe
import react.create
/**
* Main entry point for the Horse Registry JavaScript build.
*
* This function serves as the entry point for the Kotlin/JS application.
* It registers the React component as a web component using r2wc.
*/
fun main() {
console.log("Horse Registry JS module loaded successfully!")
// Import r2wc function from @r2wc/react-to-web-component npm package
val r2wc = js("require('@r2wc/react-to-web-component')")
// Convert React component to Web Component using r2wc
val PferdeListeWebComponent = r2wc(PferdeListe, js("{}"))
// Register the new component with a custom HTML tag
js("customElements.define('pferde-liste', arguments[0])")(PferdeListeWebComponent)
console.log("Web component 'pferde-liste' registered successfully!")
console.log("You can now use <pferde-liste></pferde-liste> in your HTML")
}
@@ -0,0 +1,308 @@
package at.mocode.horses.ui.components
import at.mocode.horses.domain.model.DomPferd
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import react.*
import react.dom.html.ReactHTML.div
import react.dom.html.ReactHTML.h1
import react.dom.html.ReactHTML.h3
import react.dom.html.ReactHTML.p
import react.dom.html.ReactHTML.span
import emotion.react.css
/**
* Props for the PferdeListe component
*/
external interface PferdeListeProps : Props
// Create Ktor client for API calls
private val apiClient = HttpClient {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
})
}
}
/**
* React component that displays a list of horses (Pferde).
*
* This component loads horse data from the API and renders it as HTML.
* Uses useState for state management and useEffectOnce for data loading.
*/
val PferdeListe = FC<PferdeListeProps> { _ ->
// State management with useState
var horses by useState<List<DomPferd>>(emptyList())
var loading by useState(true)
var error by useState<String?>(null)
// Data loading with useEffectOnce hook
useEffectOnce {
val scope = MainScope()
scope.launch {
try {
loading = true
error = null
// Load data with Ktor client
val response = apiClient.get("http://localhost:8080/api/horses")
val loadedHorses: List<DomPferd> = response.body()
horses = loadedHorses
} catch (e: Exception) {
error = "Fehler beim Laden der Pferde: ${e.message}"
console.error("Error loading horses:", e)
} finally {
loading = false
}
}
}
// Render HTML with React DOM elements
div {
css {
// Basic styling for the main container
"padding" to "20px"
"fontFamily" to "Arial, sans-serif"
"maxWidth" to "1200px"
"margin" to "0 auto"
}
h1 {
css {
"color" to "#2c3e50"
"borderBottom" to "2px solid #3498db"
"paddingBottom" to "10px"
"marginBottom" to "20px"
}
+"Pferde-Register"
}
when {
loading -> {
div {
css {
"padding" to "20px"
"textAlign" to "center"
"color" to "#666"
"fontSize" to "18px"
}
+"Lade Pferde..."
}
}
error != null -> {
div {
css {
"padding" to "20px"
"textAlign" to "center"
"color" to "#e74c3c"
"backgroundColor" to "#fdeaea"
"border" to "1px solid #e74c3c"
"borderRadius" to "8px"
"margin" to "20px 0"
}
+error!!
}
}
horses.isEmpty() -> {
div {
css {
"padding" to "20px"
"textAlign" to "center"
"color" to "#666"
"backgroundColor" to "#f8f9fa"
"border" to "1px solid #e0e0e0"
"borderRadius" to "8px"
"margin" to "20px 0"
}
+"Keine Pferde verfügbar"
}
}
else -> {
div {
css {
"display" to "grid"
"gridTemplateColumns" to "repeat(auto-fill, minmax(300px, 1fr))"
"gap" to "20px"
}
horses.forEach { horse ->
div {
css {
"border" to "1px solid #e0e0e0"
"borderRadius" to "8px"
"padding" to "15px"
"backgroundColor" to "#f9f9f9"
"boxShadow" to "0 2px 4px rgba(0,0,0,0.1)"
"transition" to "transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out"
"hover" to {
"transform" to "translateY(-5px)"
"boxShadow" to "0 5px 15px rgba(0,0,0,0.1)"
}
}
h3 {
css {
"color" to "#3498db"
"marginTop" to "0"
"marginBottom" to "10px"
"borderBottom" to "1px solid #e0e0e0"
"paddingBottom" to "5px"
}
+horse.getDisplayName()
}
// Basic information
p {
span {
+"🐎"
}
+" Geschlecht: ${horse.geschlecht.name}"
}
horse.geburtsdatum?.let { birthDate ->
p {
span {
+"📅"
}
+" Geburtsdatum: $birthDate"
horse.getAge()?.let { age ->
+" (${age} Jahre alt)"
}
}
}
horse.rasse?.let { breed ->
p {
span {
+"🏇"
}
+" Rasse: $breed"
}
}
horse.farbe?.let { color ->
p {
span {
+"🎨"
}
+" Farbe: $color"
}
}
horse.stockmass?.let { height ->
p {
span {
+"📏"
}
+" Stockmaß: ${height} cm"
}
}
// Identification numbers
val identificationNumbers = mutableListOf<String>()
horse.lebensnummer?.let { identificationNumbers.add("Lebensnummer: $it") }
horse.chipNummer?.let { identificationNumbers.add("Chip: $it") }
horse.passNummer?.let { identificationNumbers.add("Pass: $it") }
horse.oepsNummer?.let { identificationNumbers.add("OEPS: $it") }
horse.feiNummer?.let { identificationNumbers.add("FEI: $it") }
if (identificationNumbers.isNotEmpty()) {
p {
span {
+"🆔"
}
+" Identifikation: ${identificationNumbers.joinToString(", ")}"
}
}
// Pedigree information
val pedigreeInfo = mutableListOf<String>()
horse.vaterName?.let { pedigreeInfo.add("Vater: $it") }
horse.mutterName?.let { pedigreeInfo.add("Mutter: $it") }
horse.mutterVaterName?.let { pedigreeInfo.add("Muttervater: $it") }
if (pedigreeInfo.isNotEmpty()) {
p {
span {
+"🧬"
}
+" Abstammung: ${pedigreeInfo.joinToString(", ")}"
}
}
// Breeding information
horse.zuechterName?.let { breeder ->
p {
span {
+"👨‍🌾"
}
+" Züchter: $breeder"
}
}
horse.zuchtbuchNummer?.let { studbook ->
p {
span {
+"📖"
}
+" Zuchtbuchnummer: $studbook"
}
}
// Status indicators
val statusList = mutableListOf<String>()
if (horse.istAktiv) statusList.add("Aktiv") else statusList.add("Inaktiv")
if (horse.hasCompleteIdentification()) statusList.add("Vollständig identifiziert")
if (horse.isOepsRegistered()) statusList.add("OEPS registriert")
if (horse.isFeiRegistered()) statusList.add("FEI registriert")
p {
span {
+""
}
+" Status: ${statusList.joinToString(", ")}"
}
// Data source
p {
span {
+"📊"
}
+" Datenquelle: ${horse.datenQuelle.name}"
}
// Notes
horse.bemerkungen?.let { notes ->
p {
span {
+"📝"
}
+" Bemerkungen: $notes"
}
}
// Creation and update dates
p {
span {
+"📅"
}
+" Erstellt am: ${horse.createdAt}"
}
p {
span {
+"🔄"
}
+" Zuletzt geändert: ${horse.updatedAt}"
}
}
}
}
}
}
}
}