Refactor Veranstalter and Veranstaltung flows: add VeranstalterProfil UI, event creation callback, profile enhancements, and save-enable matrix logic. Extend ZNS import and branding workflows.
Some checks failed
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
Some checks failed
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled
This commit is contained in:
parent
f44b2c8126
commit
09debdef86
BIN
docs/06_Frontend/ErgebnisMaske/Ergebnisliste.PNG
Normal file
BIN
docs/06_Frontend/ErgebnisMaske/Ergebnisliste.PNG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
409
docs/06_Frontend/ErgebnisMaske/ergebnismaske-v01.html
Normal file
409
docs/06_Frontend/ErgebnisMaske/ergebnismaske-v01.html
Normal file
|
|
@ -0,0 +1,409 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Ergebnisliste</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Tahoma', 'Verdana', sans-serif;
|
||||||
|
background-color: #F0F0F0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: black;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#window {
|
||||||
|
border: 1px solid #999;
|
||||||
|
box-shadow: 2px 2px 5px rgba(0,0,0,0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #F0F0F0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title-bar {
|
||||||
|
background: linear-gradient(to right, #005A9C 0%, #0099CC 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 3px 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #CCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-btn {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
border: 1px solid #333;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-content {
|
||||||
|
padding: 5px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 3fr 1fr;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#left-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(12, 1fr);
|
||||||
|
gap: 1px;
|
||||||
|
background-color: #EAEAEA;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-item {
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
padding: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
min-height: 200px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #EAEAEA;
|
||||||
|
padding: 3px;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-link {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #EAEAEA;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
padding: 2px 4px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
border-bottom: 1px solid #EEE;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#middle-form {
|
||||||
|
background-color: #F0F0F0;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
padding: 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-inputs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-pair {
|
||||||
|
display: flex;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-input {
|
||||||
|
width: 25px;
|
||||||
|
border: 1px solid #AAA;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-size: 9px;
|
||||||
|
color: #555;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select {
|
||||||
|
width: 80px;
|
||||||
|
border: 1px solid #AAA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows-btn {
|
||||||
|
background-color: #EAEAEA;
|
||||||
|
border: 1px solid #AAA;
|
||||||
|
padding: 2px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 1px 1px 1px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows-btn:hover {
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
#right-panels {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel {
|
||||||
|
background-color: #F0F0F0;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-label {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 1px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number-btn {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #AAA;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: calc(100% - 100px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel-buttons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 3px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="window">
|
||||||
|
<div id="title-bar">
|
||||||
|
<span>Ergebnisliste</span>
|
||||||
|
<div id="title-controls">
|
||||||
|
<div class="title-btn">_</div>
|
||||||
|
<div class="title-btn">□</div>
|
||||||
|
<div class="title-btn">x</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="main-content">
|
||||||
|
<div id="left-column">
|
||||||
|
<div class="grid-header">
|
||||||
|
<div class="grid-item">1</div><div class="grid-item">2</div><div class="grid-item">3</div><div class="grid-item">4</div><div class="grid-item">5</div><div class="grid-item">5</div>
|
||||||
|
<div class="grid-item">6</div><div class="grid-item">7</div><div class="grid-item">8</div><div class="grid-item">9</div><div class="grid-item">10</div><div class="grid-item"></div>
|
||||||
|
<div class="grid-item">11</div><div class="grid-item">12</div><div class="grid-item"></div><div class="grid-item"></div><div class="grid-item"></div><div class="grid-item"></div>
|
||||||
|
<div class="grid-item"></div><div class="grid-item"></div><div class="grid-item"></div><div class="grid-item"></div><div class="grid-item"></div><div class="grid-item"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="toolbar">
|
||||||
|
<span class="tool-link">Aktualisieren</span>
|
||||||
|
<span class="tool-link">Platzierte</span>
|
||||||
|
<span class="tool-link">Suchen ▼</span>
|
||||||
|
<span style="color: #666;">0 gefunden</span>
|
||||||
|
<span class="tool-link">Bearbeiten</span>
|
||||||
|
</div>
|
||||||
|
<div class="pane">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Ergebnis</th><th>Nr.</th><th>KopfNr</th><th>Pferd</th><th>Reiter</th><th>K</th><th>Q</th><th>Platziert</th><th>Wertung</th><th>PGp</th><th>ZGp</th><th>Gesamtnote</th><th>Geldpreis</th><th>Pl. E</th><th>Pl. H</th><th>Pl. C</th><th>Pl. M</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="middle-form">
|
||||||
|
<div class="radio-group">
|
||||||
|
<label><input type="radio" name="parcours" checked> Grundparcours</label>
|
||||||
|
<label><input type="radio" name="parcours"> Stechen 1</label>
|
||||||
|
<label><input type="radio" name="parcours"> Stechen 2</label>
|
||||||
|
<label><input type="radio" name="parcours"> Stechen 3</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-inputs">
|
||||||
|
<div class="input-pair">
|
||||||
|
<div>
|
||||||
|
<input type="text" class="small-input">
|
||||||
|
<div class="form-label">Fehler</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="text" class="small-input">
|
||||||
|
<div class="form-label">Zeit</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<select class="form-select"></select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button class="windows-btn" disabled>Speichern</button>
|
||||||
|
<button class="windows-btn">Nächster</button>
|
||||||
|
<button class="windows-btn" disabled>Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="toolbar">
|
||||||
|
<span class="tool-link">Aktualisieren</span>
|
||||||
|
<span class="tool-link">Starter</span>
|
||||||
|
<span class="tool-link">Suchen ▼</span>
|
||||||
|
<span class="tool-link">Bearbeiten</span>
|
||||||
|
</div>
|
||||||
|
<div class="pane">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Pos.</th><th>Nr.</th><th>KopfNr</th><th>Pferd</th><th>Reiter</th><th>K</th><th>Q</th><th>Bemerkung</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="right-panels">
|
||||||
|
<div class="right-panel">
|
||||||
|
<div class="panel-title">Platzierung & Geldpreis:</div>
|
||||||
|
<div class="control-row">
|
||||||
|
<span class="right-label">Anzahl Platzierte:</span>
|
||||||
|
<div class="number-controls">
|
||||||
|
<button class="number-btn">-</button>
|
||||||
|
<input type="text" class="small-input">
|
||||||
|
<button class="number-btn">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-row">
|
||||||
|
<span class="right-label">Geldpreis:</span>
|
||||||
|
<span>---</span>
|
||||||
|
</div>
|
||||||
|
<div class="control-row">
|
||||||
|
<span class="right-label">Kaderreiter Extra:</span>
|
||||||
|
<div class="checkbox-container">
|
||||||
|
<input type="checkbox">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-row">
|
||||||
|
<span class="right-label">Anzahl platzierte Kaderreiter:</span>
|
||||||
|
<input type="text" class="small-input">
|
||||||
|
</div>
|
||||||
|
<div class="control-row">
|
||||||
|
<span class="right-label">Geldpreis für Kaderreiter:</span>
|
||||||
|
<span>---</span>
|
||||||
|
</div>
|
||||||
|
<div class="control-row">
|
||||||
|
<span class="right-label">Summe Geldpreise:</span>
|
||||||
|
<span>---</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-panel">
|
||||||
|
<div class="panel-title">Bewerb:</div>
|
||||||
|
<div class="right-panel-buttons">
|
||||||
|
<button class="windows-btn">Abschließen</button>
|
||||||
|
<button class="windows-btn">Öffnen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-panel">
|
||||||
|
<div class="panel-title">Ergebnisliste:</div>
|
||||||
|
<div class="right-panel-buttons">
|
||||||
|
<button class="windows-btn">Import</button>
|
||||||
|
<button class="windows-btn">Pfad</button>
|
||||||
|
<button class="windows-btn">Export</button>
|
||||||
|
<button class="windows-btn">Drucken</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
323
docs/06_Frontend/Nennmaske/nennmaske-v01.html
Normal file
323
docs/06_Frontend/Nennmaske/nennmaske-v01.html
Normal file
|
|
@ -0,0 +1,323 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Nennmaske Entwurf</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-color: #f4f7f6;
|
||||||
|
--border-color: #d1d5db;
|
||||||
|
--primary-blue: #3f51b5;
|
||||||
|
--text-main: #333;
|
||||||
|
--header-bg: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Layout Struktur */
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr auto 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
height: 95vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1.2fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.5fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gemeinsame Panel-Styles */
|
||||||
|
.panel {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
padding: 8px;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #fff;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Formular Elemente */
|
||||||
|
input[type="text"] {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 5px 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-bar-center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn {
|
||||||
|
background: var(--primary-blue);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabellen Styles */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #f9fafb;
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-selected {
|
||||||
|
background-color: #fff9c4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spezifische Anpassungen */
|
||||||
|
.footer-btns {
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-btns button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
padding: 8px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
color: var(--primary-blue);
|
||||||
|
border-bottom: 2px solid var(--primary-blue);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qty-input {
|
||||||
|
width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="top-row">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<strong>Pferd:</strong>
|
||||||
|
<input type="text" placeholder="Kopfnummer oder Name">
|
||||||
|
<button>...</button>
|
||||||
|
<button>Leeren</button>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<div class="empty-state">Keine Ergebnisse</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-btns">
|
||||||
|
<button class="btn-primary">Neu</button>
|
||||||
|
<button disabled>Bearbeiten</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<strong>Reiter:</strong>
|
||||||
|
<input type="text" placeholder="Vorname und/oder Nachname">
|
||||||
|
<button>...</button>
|
||||||
|
<button>Leeren</button>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<div class="empty-state">Keine Ergebnisse</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-btns">
|
||||||
|
<button class="btn-primary">Neu</button>
|
||||||
|
<button disabled>Bearbeiten</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<div class="tabs">
|
||||||
|
<div class="tab active">VERKAUF</div>
|
||||||
|
<div class="tab">BUCHUNGEN</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-header" style="justify-content: space-between;">
|
||||||
|
<span>11 Artikel</span>
|
||||||
|
<div>
|
||||||
|
<a href="#" style="font-size: 11px; margin-right: 10px;">Rückgängig</a>
|
||||||
|
<a href="#" style="font-size: 11px; font-weight: bold;">Speichern</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>KNr</th>
|
||||||
|
<th>+/-</th>
|
||||||
|
<th>Menge</th>
|
||||||
|
<th>Buchungstext</th>
|
||||||
|
<th>Betrag</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td></td><td>+</td><td><input class="qty-input" value="0"></td><td>Belastung</td><td>0.00</td></tr>
|
||||||
|
<tr class="row-selected"><td></td><td>+</td><td><input class="qty-input" value="0"></td><td>Gutschrift</td><td>0.00</td></tr>
|
||||||
|
<tr><td></td><td>+</td><td><input class="qty-input" value="0"></td><td>Boxenpauschale</td><td>0.00</td></tr>
|
||||||
|
<tr><td></td><td>+</td><td><input class="qty-input" value="0"></td><td>Ansage</td><td>0.00</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-bar-center">
|
||||||
|
<button class="nav-btn">☰ Startliste</button>
|
||||||
|
<button class="nav-btn">🏆 Ergebnisse</button>
|
||||||
|
<button class="nav-btn">📄 Abrechnung</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bottom-row">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="tabs">
|
||||||
|
<div class="tab active">REITER</div>
|
||||||
|
<div class="tab">PFERD</div>
|
||||||
|
<div class="tab">BEWERBE</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-header" style="justify-content: space-between; background: #fafafa;">
|
||||||
|
<span>0 Nennungen</span>
|
||||||
|
<div style="color: var(--primary-blue);">
|
||||||
|
<span style="margin-right: 10px; cursor: pointer;">Positionieren</span>
|
||||||
|
<span style="cursor: pointer;">Stornieren</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Tag</th>
|
||||||
|
<th>Pl.</th>
|
||||||
|
<th>Bewerb</th>
|
||||||
|
<th>Bewerbsname</th>
|
||||||
|
<th>Bemerkung</th>
|
||||||
|
<th>Pferd</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
<div class="empty-state" style="margin-top: 50px;">Keine Nennungen vorhanden</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-header" style="justify-content: space-between;">
|
||||||
|
<strong>Bewerbsübersicht</strong>
|
||||||
|
</div>
|
||||||
|
<div class="panel-header" style="justify-content: space-between; font-size: 11px;">
|
||||||
|
<span>12 Bewerbe</span>
|
||||||
|
<span style="color: var(--primary-blue);">Filtern</span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Tag</th>
|
||||||
|
<th>Pl.</th>
|
||||||
|
<th>Bew.</th>
|
||||||
|
<th>Beginn</th>
|
||||||
|
<th>Nenn.</th>
|
||||||
|
<th>Bewerbsname</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>So</td><td>1</td><td><strong>1</strong></td><td>08:00</td><td>0</td><td>Dressurreiterprüfung Ratepass</td></tr>
|
||||||
|
<tr><td>So</td><td>1</td><td><strong>2</strong></td><td>08:20</td><td>0</td><td>Dressurreiterprüfung Katecnadel</td></tr>
|
||||||
|
<tr><td>So</td><td>1</td><td><strong>3</strong></td><td>08:40</td><td>0</td><td>Dressurreiterprüfung Idf.</td></tr>
|
||||||
|
<tr><td>So</td><td>1</td><td><strong>4</strong></td><td>09:00</td><td>0</td><td>Dressurprüfung Idf.</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 5px; text-align: center; font-size: 11px; color: #777;">
|
||||||
|
Bitte wählen Sie zuerst ein Pferd und einen Reiter aus
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
docs/06_Frontend/flow-fehler.png
Normal file
BIN
docs/06_Frontend/flow-fehler.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
BIN
docs/06_Frontend/flow-wechsel.png
Normal file
BIN
docs/06_Frontend/flow-wechsel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 962 KiB |
BIN
docs/06_Frontend/verlauf-neueVeranstaltungTurnier-anlegen.png
Normal file
BIN
docs/06_Frontend/verlauf-neueVeranstaltungTurnier-anlegen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 MiB |
|
|
@ -0,0 +1,47 @@
|
||||||
|
# 🧹 Curator – Session Log (2026-04-01)
|
||||||
|
|
||||||
|
## Zusammenfassung
|
||||||
|
- Flow-Entscheidung bestätigt: Grüner Pfad aktiv, roter Pfad verworfen. "+ Neues Turnier" führt direkt zum Tab „STAMMDATEN“ v2 mit Turnier‑Nr.-Gatekeeping.
|
||||||
|
- Keine Codeänderungen in dieser Sitzung; Build zuletzt grün. Entscheidungen und nächste Schritte dokumentiert.
|
||||||
|
|
||||||
|
## Beschlossene UI/Flow-Regeln
|
||||||
|
- Turnieranlage
|
||||||
|
- Einstieg: "+ Neues Turnier" → direkt „Turnier Detail v2“ Tab „STAMMDATEN“.
|
||||||
|
- Gatekeeping: 5‑stellige Turnier‑Nr. eingeben + Bestätigungsdialog (danach immutable).
|
||||||
|
- Save-Enable-Matrix: aktiv nur wenn (Nr bestätigt ∧ ZNS geladen ∧ Datum gültig).
|
||||||
|
- ZNS-Status
|
||||||
|
- Panel immer sichtbar, zeigt Quelle, `payloadVersion`, Zeitstempel.
|
||||||
|
- „Import‑Log“ Dialog mit den letzten 5 Einträgen (Erfolg/Fehler, Kurzmeldung).
|
||||||
|
- Kategorien & Pony
|
||||||
|
- Mehrfach-Kategorien wie vormittags vereinbart; Pony über Kategorien‑Suffix „P“ (kein separater Switch).
|
||||||
|
- Kategorien-UI wird gruppiert (z. B. Dressur/Springen).
|
||||||
|
- Datum/Ort
|
||||||
|
- Datum im zulässigen Veranstaltungszeitraum; Hinweis: „Muss zwischen [von–bis] liegen“.
|
||||||
|
- Abweichender Turnier‑Ort: Soft‑Warnung (kein Hard‑Block).
|
||||||
|
- Branding
|
||||||
|
- Feld „Titel“ optional. Default‑Vorschlag: „[Kategorien] [Verein‑Ort] [Bundesland]“ (Fallback über Veranstalterdaten).
|
||||||
|
- „Turnier‑Logo“ optional; Fallback = Veranstalter‑Logo.
|
||||||
|
|
||||||
|
## Veranstalter-Flow
|
||||||
|
- Nach „Schritt 2: Vereinsdaten bestätigen“ → Weiterleitung zum „Veranstalter‑Profil“.
|
||||||
|
- Veranstalter‑Profil: minimale Felder (Logo‑URL, Ansprechpartner, E‑Mail, Telefon, Adresse), CTA „+ Neue Veranstaltung“.
|
||||||
|
- Von dort → Veranstaltung‑Wizard Schritt 2 („Basisdaten“). Feld „Veranstaltungs‑Logo“ optional; Fallbacks: Veranstaltungs‑Logo → Veranstalter‑Logo → Default.
|
||||||
|
|
||||||
|
## Footer-Onboarding
|
||||||
|
- Online/Offline‑Status anzeigen.
|
||||||
|
- Geräte‑Verbindung (z. B. „Richter‑Turm“) anzeigen, klickbar für Details.
|
||||||
|
- Chat‑Trigger anzeigen, wenn mindestens ein weiteres Gerät verbunden ist.
|
||||||
|
|
||||||
|
## Nächste Schritte (To‑Do)
|
||||||
|
- Routing final auf Stammdaten v2 festziehen; alte Pfade entfernen.
|
||||||
|
- Save‑Enable‑Matrix implementieren; ZNS‑Panel inkl. Import‑Log.
|
||||||
|
- Kategorien‑UI konsolidieren und gruppieren; Default‑Titel generieren; Ort‑Softwarnung.
|
||||||
|
- Veranstalter‑Profil & ‑Übersicht finalisieren; CTA‑Flow prüfen.
|
||||||
|
- Footer‑Onboarding integrieren (Status, Geräte, Chat‑Trigger).
|
||||||
|
|
||||||
|
## Artefakte/Referenzen
|
||||||
|
- docs/06_Frontend/flow-wechsel.png (neuer Flow – grüner Pfeil)
|
||||||
|
- docs/06_Frontend/flow-fehler.png (Bruchstellen im alten Flow)
|
||||||
|
- docs/99_Journal/2026-03-31_Session_Log_Event_First_Workflow.md
|
||||||
|
- docs/99_Journal/2026-03-30_Session_Log_ZNS_Documentation.md
|
||||||
|
- docs/99_Journal/2026-03-30_Session_Log_Masterdata_OETO_Consolidation.md
|
||||||
|
|
@ -33,6 +33,13 @@ fun TurnierDetailScreen(
|
||||||
veranstaltungId: Long,
|
veranstaltungId: Long,
|
||||||
turnierId: Long,
|
turnierId: Long,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
|
eventVon: String? = null,
|
||||||
|
eventBis: String? = null,
|
||||||
|
eventOrt: String? = null,
|
||||||
|
veranstalterName: String? = null,
|
||||||
|
veranstalterOrt: String? = null,
|
||||||
|
veranstalterBundesland: String? = null,
|
||||||
|
veranstalterLogoUrl: String? = null,
|
||||||
) {
|
) {
|
||||||
var selectedTab by remember { mutableIntStateOf(0) }
|
var selectedTab by remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
|
|
@ -80,7 +87,16 @@ fun TurnierDetailScreen(
|
||||||
// Tab-Inhalte
|
// Tab-Inhalte
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
when (selectedTab) {
|
when (selectedTab) {
|
||||||
0 -> StammdatenTabContent(turnierId = turnierId)
|
0 -> StammdatenTabContent(
|
||||||
|
turnierId = turnierId,
|
||||||
|
eventVon = eventVon,
|
||||||
|
eventBis = eventBis,
|
||||||
|
eventOrt = eventOrt,
|
||||||
|
veranstalterName = veranstalterName,
|
||||||
|
veranstalterOrt = veranstalterOrt,
|
||||||
|
veranstalterBundesland = veranstalterBundesland,
|
||||||
|
veranstalterLogoUrl = veranstalterLogoUrl,
|
||||||
|
)
|
||||||
1 -> OrganisationTabContent()
|
1 -> OrganisationTabContent()
|
||||||
2 -> BewerbeTabContent()
|
2 -> BewerbeTabContent()
|
||||||
3 -> ArtikelTabContent()
|
3 -> ArtikelTabContent()
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,26 @@ private val AccentBlue = Color(0xFF3B82F6)
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun StammdatenTabContent(turnierId: Long) {
|
fun StammdatenTabContent(
|
||||||
|
turnierId: Long,
|
||||||
|
eventVon: String? = null,
|
||||||
|
eventBis: String? = null,
|
||||||
|
eventOrt: String? = null,
|
||||||
|
veranstalterName: String? = null,
|
||||||
|
veranstalterOrt: String? = null,
|
||||||
|
veranstalterBundesland: String? = null,
|
||||||
|
veranstalterLogoUrl: String? = null,
|
||||||
|
) {
|
||||||
// In einer echten App würden wir diese Daten aus einem ViewModel laden.
|
// In einer echten App würden wir diese Daten aus einem ViewModel laden.
|
||||||
// Hier simulieren wir den State basierend auf den Anforderungen.
|
// Hier simulieren wir den State basierend auf den Anforderungen.
|
||||||
|
|
||||||
var turnierNr by remember { mutableStateOf("") }
|
var turnierNr by remember { mutableStateOf("") }
|
||||||
var nrConfirmed by remember { mutableStateOf(false) }
|
var nrConfirmed by remember { mutableStateOf(false) }
|
||||||
|
var showNrConfirm by remember { mutableStateOf(false) }
|
||||||
var znsDataLoaded by remember { mutableStateOf(false) }
|
var znsDataLoaded by remember { mutableStateOf(false) }
|
||||||
|
var znsPayloadVersion by remember { mutableStateOf<String?>(null) }
|
||||||
|
var znsImportedAt by remember { mutableStateOf<String?>(null) }
|
||||||
|
val znsImportHistory = remember { mutableStateListOf<Triple<String, String, Boolean>>() } // (source, payloadVersion, ok)
|
||||||
var typ by remember { mutableStateOf("ÖTO (National)") }
|
var typ by remember { mutableStateOf("ÖTO (National)") }
|
||||||
|
|
||||||
val sparten = remember { mutableStateListOf<String>() }
|
val sparten = remember { mutableStateListOf<String>() }
|
||||||
|
|
@ -48,9 +61,11 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
|
|
||||||
var titel by remember { mutableStateOf("") }
|
var titel by remember { mutableStateOf("") }
|
||||||
var subTitel by remember { mutableStateOf("") }
|
var subTitel by remember { mutableStateOf("") }
|
||||||
|
var turnierLogoUrl by remember { mutableStateOf("") }
|
||||||
val sponsoren = remember { mutableStateListOf<String>() }
|
val sponsoren = remember { mutableStateListOf<String>() }
|
||||||
|
|
||||||
var showZnsDialog by remember { mutableStateOf(false) }
|
var showZnsDialog by remember { mutableStateOf(false) }
|
||||||
|
var showZnsLog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// Hilfs-States für DatePicker
|
// Hilfs-States für DatePicker
|
||||||
var showDatePickerVon by remember { mutableStateOf(false) }
|
var showDatePickerVon by remember { mutableStateOf(false) }
|
||||||
|
|
@ -79,7 +94,7 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
)
|
)
|
||||||
if (!nrConfirmed) {
|
if (!nrConfirmed) {
|
||||||
Button(
|
Button(
|
||||||
onClick = { nrConfirmed = true },
|
onClick = { showNrConfirm = true },
|
||||||
enabled = turnierNr.length == 5,
|
enabled = turnierNr.length == 5,
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue)
|
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue)
|
||||||
) {
|
) {
|
||||||
|
|
@ -88,9 +103,9 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
} else {
|
} else {
|
||||||
InputChip(
|
InputChip(
|
||||||
selected = true,
|
selected = true,
|
||||||
onClick = { nrConfirmed = false },
|
onClick = { },
|
||||||
label = { Text("Bestätigt") },
|
label = { Text("Bestätigt") },
|
||||||
trailingIcon = { Icon(Icons.Default.Edit, contentDescription = null, modifier = Modifier.size(16.dp)) }
|
trailingIcon = { Icon(Icons.Default.Check, contentDescription = null, modifier = Modifier.size(16.dp)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,11 +123,13 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = typ == "ÖTO (National)",
|
selected = typ == "ÖTO (National)",
|
||||||
onClick = { typ = "ÖTO (National)" },
|
onClick = { typ = "ÖTO (National)" },
|
||||||
|
enabled = nrConfirmed,
|
||||||
label = { Text("ÖTO (National)") }
|
label = { Text("ÖTO (National)") }
|
||||||
)
|
)
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = typ == "FEI (International)",
|
selected = typ == "FEI (International)",
|
||||||
onClick = { typ = "FEI (International)" },
|
onClick = { typ = "FEI (International)" },
|
||||||
|
enabled = nrConfirmed,
|
||||||
label = { Text("FEI (International)") }
|
label = { Text("FEI (International)") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -123,16 +140,18 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
Button(
|
Button(
|
||||||
onClick = { showZnsDialog = true },
|
onClick = { showZnsDialog = true },
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = AccentBlue)
|
colors = ButtonDefaults.buttonColors(containerColor = AccentBlue)
|
||||||
|
, enabled = nrConfirmed
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.CloudDownload, contentDescription = null, modifier = Modifier.size(18.dp))
|
Icon(Icons.Default.CloudDownload, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Text("Import via Internet")
|
Text("Import via Internet")
|
||||||
}
|
}
|
||||||
OutlinedButton(onClick = { showZnsDialog = true }) {
|
OutlinedButton(onClick = { showZnsDialog = true }, enabled = nrConfirmed) {
|
||||||
Icon(Icons.Default.Usb, contentDescription = null, modifier = Modifier.size(18.dp))
|
Icon(Icons.Default.Usb, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Text("Import via USB")
|
Text("Import via USB")
|
||||||
}
|
}
|
||||||
|
TextButton(onClick = { showZnsLog = true }, enabled = nrConfirmed) { Text("Import-Log anzeigen…") }
|
||||||
}
|
}
|
||||||
|
|
||||||
val znsStatusColor = if (znsDataLoaded) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error
|
val znsStatusColor = if (znsDataLoaded) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error
|
||||||
|
|
@ -149,6 +168,17 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 13.sp
|
fontSize = 13.sp
|
||||||
)
|
)
|
||||||
|
if (znsDataLoaded) {
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
listOfNotNull(
|
||||||
|
znsPayloadVersion?.let { "Version: $it" },
|
||||||
|
znsImportedAt?.let { "Zeit: $it" },
|
||||||
|
).joinToString(" • "),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -160,11 +190,13 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = sparten.contains("Dressur"),
|
selected = sparten.contains("Dressur"),
|
||||||
onClick = { if (sparten.contains("Dressur")) sparten.remove("Dressur") else sparten.add("Dressur") },
|
onClick = { if (sparten.contains("Dressur")) sparten.remove("Dressur") else sparten.add("Dressur") },
|
||||||
|
enabled = nrConfirmed,
|
||||||
label = { Text("Dressur") }
|
label = { Text("Dressur") }
|
||||||
)
|
)
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = sparten.contains("Springen"),
|
selected = sparten.contains("Springen"),
|
||||||
onClick = { if (sparten.contains("Springen")) sparten.remove("Springen") else sparten.add("Springen") },
|
onClick = { if (sparten.contains("Springen")) sparten.remove("Springen") else sparten.add("Springen") },
|
||||||
|
enabled = nrConfirmed,
|
||||||
label = { Text("Springen") }
|
label = { Text("Springen") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -177,6 +209,7 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = klassen.contains(k),
|
selected = klassen.contains(k),
|
||||||
onClick = { if (klassen.contains(k)) klassen.remove(k) else klassen.add(k) },
|
onClick = { if (klassen.contains(k)) klassen.remove(k) else klassen.add(k) },
|
||||||
|
enabled = nrConfirmed,
|
||||||
label = { Text(k) }
|
label = { Text(k) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -197,64 +230,112 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
if (suggested.isEmpty()) {
|
if (suggested.isEmpty()) {
|
||||||
Text("Bitte Sparte und Klasse wählen", color = Color.Gray, fontSize = 13.sp)
|
Text("Bitte Sparte und Klasse wählen", color = Color.Gray, fontSize = 13.sp)
|
||||||
} else {
|
} else {
|
||||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
// Gruppiere nach Sparte (CDN/CSN)
|
||||||
suggested.forEach { c ->
|
val grouped = suggested.groupBy { if (it.startsWith("CDN")) "Dressur" else "Springen" }
|
||||||
InputChip(
|
grouped.forEach { (gruppe, eintraege) ->
|
||||||
selected = kat.contains(c),
|
Text(gruppe, fontWeight = FontWeight.SemiBold, color = PrimaryBlue)
|
||||||
onClick = { if (kat.contains(c)) kat.remove(c) else kat.add(c) },
|
Spacer(Modifier.height(4.dp))
|
||||||
label = { Text(c) }
|
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
)
|
eintraege.sorted().forEach { c ->
|
||||||
|
InputChip(
|
||||||
|
selected = kat.contains(c),
|
||||||
|
onClick = { if (kat.contains(c)) kat.remove(c) else kat.add(c) },
|
||||||
|
enabled = nrConfirmed,
|
||||||
|
label = { Text(c) }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FormRow("Zeitraum:") {
|
FormRow("Zeitraum:") {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
val vonMod = if (nrConfirmed) Modifier.width(160.dp).clickable { showDatePickerVon = true } else Modifier.width(160.dp)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = von,
|
value = von,
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
label = { Text("Von") },
|
label = { Text("Von") },
|
||||||
modifier = Modifier.width(160.dp).clickable { showDatePickerVon = true },
|
modifier = vonMod,
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
|
enabled = nrConfirmed,
|
||||||
trailingIcon = { Icon(Icons.Default.DateRange, null) }
|
trailingIcon = { Icon(Icons.Default.DateRange, null) }
|
||||||
)
|
)
|
||||||
Text("bis")
|
Text("bis")
|
||||||
|
val bisMod = if (nrConfirmed) Modifier.width(160.dp).clickable { showDatePickerBis = true } else Modifier.width(160.dp)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = bis,
|
value = bis,
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
label = { Text("Bis") },
|
label = { Text("Bis") },
|
||||||
modifier = Modifier.width(160.dp).clickable { showDatePickerBis = true },
|
modifier = bisMod,
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
|
enabled = nrConfirmed,
|
||||||
trailingIcon = { Icon(Icons.Default.DateRange, null) }
|
trailingIcon = { Icon(Icons.Default.DateRange, null) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text("Hinweis: Muss innerhalb des Veranstaltungs-Zeitraums liegen.", fontSize = 11.sp, color = Color.Gray)
|
val rangeText = if (eventVon != null && eventBis != null) "Muss zwischen $eventVon – $eventBis liegen." else "Muss innerhalb des Veranstaltungs-Zeitraums liegen."
|
||||||
}
|
Text(rangeText, fontSize = 11.sp, color = Color.Gray)
|
||||||
|
|
||||||
FormRow("Ort:") {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = ort,
|
|
||||||
onValueChange = { ort = it },
|
|
||||||
label = { Text("Austragungsort") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
supportingText = { Text("Muss mit Veranstaltungsort übereinstimmen.") }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Branding (Schritt 3 Logik) ───────────────────────────────────────
|
// ── Branding (Schritt 3 Logik) ───────────────────────────────────────
|
||||||
SectionCard(title = "Turnier-Branding") {
|
SectionCard(title = "Turnier-Branding") {
|
||||||
|
// Default-Titel-Vorschlag: [Kategorien] [Verein-Ort] [Bundesland]
|
||||||
|
val defaultTitle = remember(kat.size, veranstalterOrt, veranstalterBundesland) {
|
||||||
|
val cats = if (kat.isEmpty()) "" else kat.sorted().joinToString(" ")
|
||||||
|
listOfNotNull(cats.ifBlank { null },
|
||||||
|
listOfNotNull(veranstalterOrt, veranstalterBundesland).filter { it.isNotBlank() }.joinToString(" ")
|
||||||
|
.takeIf { it.isNotBlank() }
|
||||||
|
).joinToString(" ")
|
||||||
|
}
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = titel,
|
value = titel,
|
||||||
onValueChange = { titel = it },
|
onValueChange = { titel = it },
|
||||||
label = { Text("Titel") },
|
label = { Text("Titel") },
|
||||||
|
placeholder = { if (defaultTitle.isNotBlank()) Text(defaultTitle) },
|
||||||
|
enabled = nrConfirmed,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = subTitel,
|
value = subTitel,
|
||||||
onValueChange = { subTitel = it },
|
onValueChange = { subTitel = it },
|
||||||
label = { Text("Sub-Titel") },
|
label = { Text("Sub-Titel") },
|
||||||
|
enabled = nrConfirmed,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ort im Branding-Bereich platzieren (mit Soft-Warnung bei Abweichung zum Veranstaltungsort)
|
||||||
|
FormRow("Ort:") {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = ort,
|
||||||
|
onValueChange = { ort = it },
|
||||||
|
label = { Text("Austragungsort") },
|
||||||
|
enabled = nrConfirmed,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
supportingText = {
|
||||||
|
if (eventOrt != null && ort.isNotBlank() && ort.trim() != eventOrt.trim()) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Icon(Icons.Default.Warning, contentDescription = null, tint = Color(0xFFF59E0B), modifier = Modifier.size(14.dp))
|
||||||
|
Spacer(Modifier.width(4.dp))
|
||||||
|
Text("Abweichung zum Veranstaltungsort ($eventOrt) – bitte prüfen.", color = Color(0xFFF59E0B))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("Muss mit Veranstaltungsort übereinstimmen.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turnier-Logo mit Fallback auf Veranstalterlogo
|
||||||
|
OutlinedTextField(
|
||||||
|
value = turnierLogoUrl,
|
||||||
|
onValueChange = { turnierLogoUrl = it },
|
||||||
|
label = { Text("Turnier-Logo (URL/Pfad)") },
|
||||||
|
enabled = nrConfirmed,
|
||||||
|
supportingText = {
|
||||||
|
Text("Wenn leer: verwende Veranstalter-Logo${if (!veranstalterLogoUrl.isNullOrBlank()) " ($veranstalterLogoUrl)" else ""}.")
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -264,11 +345,12 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
InputChip(
|
InputChip(
|
||||||
selected = true,
|
selected = true,
|
||||||
onClick = { sponsoren.remove(s) },
|
onClick = { sponsoren.remove(s) },
|
||||||
|
enabled = nrConfirmed,
|
||||||
label = { Text(s) },
|
label = { Text(s) },
|
||||||
trailingIcon = { Icon(Icons.Default.Close, null, modifier = Modifier.size(14.dp)) }
|
trailingIcon = { Icon(Icons.Default.Close, null, modifier = Modifier.size(14.dp)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
TextButton(onClick = { sponsoren.add("Neuer Sponsor") }) {
|
TextButton(onClick = { sponsoren.add("Neuer Sponsor") }, enabled = nrConfirmed) {
|
||||||
Text("+ Hinzufügen")
|
Text("+ Hinzufügen")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -276,10 +358,46 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Footer ──────────────────────────────────────────────────────────
|
// ── Footer ──────────────────────────────────────────────────────────
|
||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
// Save-Enable-Matrix (kleine Checkliste)
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
AssistChip(onClick = {}, label = { Text("Nr bestätigt") }, leadingIcon = {
|
||||||
|
Icon(if (nrConfirmed) Icons.Default.Check else Icons.Default.Close, null, tint = if (nrConfirmed) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error)
|
||||||
|
})
|
||||||
|
AssistChip(onClick = {}, label = { Text("ZNS geladen") }, leadingIcon = {
|
||||||
|
Icon(if (znsDataLoaded) Icons.Default.Check else Icons.Default.Close, null, tint = if (znsDataLoaded) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error)
|
||||||
|
})
|
||||||
|
val dateOk = remember(von, bis, eventVon, eventBis) {
|
||||||
|
try {
|
||||||
|
if (eventVon == null || eventBis == null || von.isBlank()) true else {
|
||||||
|
val evV = LocalDate.parse(eventVon)
|
||||||
|
val evB = LocalDate.parse(eventBis)
|
||||||
|
val tV = LocalDate.parse(von)
|
||||||
|
val tB = if (bis.isBlank()) tV else LocalDate.parse(bis)
|
||||||
|
!tV.isBefore(evV) && !tB.isAfter(evB) && !tB.isBefore(tV)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) { false }
|
||||||
|
}
|
||||||
|
AssistChip(onClick = {}, label = { Text("Datum gültig") }, leadingIcon = {
|
||||||
|
Icon(if (dateOk) Icons.Default.Check else Icons.Default.Close, null, tint = if (dateOk) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = { /* Speichern */ },
|
onClick = { /* Speichern */ },
|
||||||
enabled = nrConfirmed && znsDataLoaded && kat.isNotEmpty() && von.isNotBlank() && titel.isNotBlank(),
|
enabled = run {
|
||||||
|
val base = nrConfirmed && znsDataLoaded && kat.isNotEmpty() && von.isNotBlank()
|
||||||
|
val dateValid = try {
|
||||||
|
if (eventVon == null || eventBis == null || von.isBlank()) true else {
|
||||||
|
val evV = LocalDate.parse(eventVon)
|
||||||
|
val evB = LocalDate.parse(eventBis)
|
||||||
|
val tV = LocalDate.parse(von)
|
||||||
|
val tB = if (bis.isBlank()) tV else LocalDate.parse(bis)
|
||||||
|
!tV.isBefore(evV) && !tB.isAfter(evB) && !tB.isBefore(tV)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) { false }
|
||||||
|
base && dateValid
|
||||||
|
},
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
||||||
modifier = Modifier.padding(bottom = 24.dp)
|
modifier = Modifier.padding(bottom = 24.dp)
|
||||||
) {
|
) {
|
||||||
|
|
@ -297,7 +415,13 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
title = { Text("ZNS Import") },
|
title = { Text("ZNS Import") },
|
||||||
text = { Text("Simuliere ZNS-Stammdaten Import für Turnier #$turnierNr...") },
|
text = { Text("Simuliere ZNS-Stammdaten Import für Turnier #$turnierNr...") },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = { znsDataLoaded = true; showZnsDialog = false }) { Text("Importieren") }
|
TextButton(onClick = {
|
||||||
|
znsDataLoaded = true
|
||||||
|
znsPayloadVersion = "v2.4"
|
||||||
|
znsImportedAt = java.time.Instant.now().toString()
|
||||||
|
znsImportHistory.add(Triple("Internet/USB", znsPayloadVersion!!, true))
|
||||||
|
showZnsDialog = false
|
||||||
|
}) { Text("Importieren") }
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { showZnsDialog = false }) { Text("Abbrechen") }
|
TextButton(onClick = { showZnsDialog = false }) { Text("Abbrechen") }
|
||||||
|
|
@ -305,6 +429,40 @@ fun StammdatenTabContent(turnierId: Long) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showNrConfirm) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showNrConfirm = false },
|
||||||
|
title = { Text("Turnier-Nummer bestätigen?") },
|
||||||
|
text = { Text("Die Turnier-Nr. ist nach der Bestätigung nicht mehr änderbar.") },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { nrConfirmed = true; showNrConfirm = false }) { Text("Ja, bestätigen") }
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showNrConfirm = false }) { Text("Abbrechen") }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showZnsLog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showZnsLog = false },
|
||||||
|
title = { Text("ZNS Import-Log (letzte 5)") },
|
||||||
|
text = {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
|
if (znsImportHistory.isEmpty()) {
|
||||||
|
Text("Keine Einträge vorhanden.", color = Color.Gray)
|
||||||
|
} else {
|
||||||
|
znsImportHistory.takeLast(5).asReversed().forEach { (src, ver, ok) ->
|
||||||
|
val c = if (ok) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error
|
||||||
|
Text("• $src – Version $ver – ${if (ok) "OK" else "Fehler"}", color = c, fontSize = 13.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = { TextButton(onClick = { showZnsLog = false }) { Text("Schließen") } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (showDatePickerVon) {
|
if (showDatePickerVon) {
|
||||||
val state = rememberDatePickerState()
|
val state = rememberDatePickerState()
|
||||||
DatePickerDialog(
|
DatePickerDialog(
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,17 @@ import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Chat
|
||||||
|
import androidx.compose.material.icons.filled.Devices
|
||||||
|
import androidx.compose.material.icons.filled.Wifi
|
||||||
|
import androidx.compose.material.icons.filled.WifiOff
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
@ -57,11 +64,14 @@ fun DesktopMainLayout(
|
||||||
onNavigate = onNavigate,
|
onNavigate = onNavigate,
|
||||||
onLogout = onLogout,
|
onLogout = onLogout,
|
||||||
)
|
)
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
DesktopContentArea(
|
Box(modifier = Modifier.weight(1f).fillMaxWidth()) {
|
||||||
currentScreen = currentScreen,
|
DesktopContentArea(
|
||||||
onNavigate = onNavigate,
|
currentScreen = currentScreen,
|
||||||
)
|
onNavigate = onNavigate,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DesktopFooterBar()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -259,6 +269,20 @@ private fun DesktopTopBar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hilfsfunktion: OEPS-Bundeslandcode → Abkürzung
|
||||||
|
private fun mapOepsToBundesland(code: String): String = when (code.uppercase()) {
|
||||||
|
"OOE" -> "OÖ"
|
||||||
|
"NOE" -> "NÖ"
|
||||||
|
"ST" -> "Stmk."
|
||||||
|
"W" -> "Wien"
|
||||||
|
"BGLD", "B" -> "Bgld."
|
||||||
|
"K" -> "Ktn."
|
||||||
|
"S" -> "Sbg."
|
||||||
|
"T" -> "Tirol"
|
||||||
|
"V" -> "Vbg."
|
||||||
|
else -> code
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BreadcrumbSeparator() {
|
private fun BreadcrumbSeparator() {
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -344,7 +368,8 @@ private fun DesktopContentArea(
|
||||||
if (vId == 0L) onNavigate(AppScreen.Veranstaltungen)
|
if (vId == 0L) onNavigate(AppScreen.Veranstaltungen)
|
||||||
else onNavigate(AppScreen.VeranstalterDetail(vId))
|
else onNavigate(AppScreen.VeranstalterDetail(vId))
|
||||||
},
|
},
|
||||||
onSaved = { evtId, finalVId -> onNavigate(AppScreen.VeranstaltungUebersicht(finalVId, evtId)) }
|
onSaved = { evtId, finalVId -> onNavigate(AppScreen.VeranstaltungUebersicht(finalVId, evtId)) },
|
||||||
|
onVeranstalterCreated = { newVId -> onNavigate(AppScreen.VeranstalterDetail(newVId)) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is AppScreen.VeranstaltungUebersicht -> {
|
is AppScreen.VeranstaltungUebersicht -> {
|
||||||
|
|
@ -365,7 +390,20 @@ private fun DesktopContentArea(
|
||||||
veranstalterId = vId,
|
veranstalterId = vId,
|
||||||
veranstaltungId = evtId,
|
veranstaltungId = evtId,
|
||||||
onBack = { onNavigate(AppScreen.VeranstalterDetail(vId)) },
|
onBack = { onNavigate(AppScreen.VeranstalterDetail(vId)) },
|
||||||
onTurnierNeu = { onNavigate(AppScreen.TurnierNeu(evtId)) },
|
onTurnierNeu = {
|
||||||
|
val veranstaltung = at.mocode.desktop.v2.StoreV2.eventsFor(vId).firstOrNull { it.id == evtId }
|
||||||
|
val list = at.mocode.desktop.v2.TurnierStoreV2.list(evtId)
|
||||||
|
val newId = (list.maxOfOrNull { it.id } ?: 0L) + 1L
|
||||||
|
val draft = at.mocode.desktop.v2.TurnierV2(
|
||||||
|
id = newId,
|
||||||
|
veranstaltungId = evtId,
|
||||||
|
turnierNr = 0,
|
||||||
|
datumVon = veranstaltung?.datumVon ?: "",
|
||||||
|
datumBis = veranstaltung?.datumBis,
|
||||||
|
)
|
||||||
|
at.mocode.desktop.v2.TurnierStoreV2.add(evtId, draft)
|
||||||
|
onNavigate(AppScreen.TurnierDetail(evtId, newId))
|
||||||
|
},
|
||||||
onTurnierOpen = { tId -> onNavigate(AppScreen.TurnierDetail(evtId, tId)) },
|
onTurnierOpen = { tId -> onNavigate(AppScreen.TurnierDetail(evtId, tId)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -375,7 +413,23 @@ private fun DesktopContentArea(
|
||||||
is AppScreen.VeranstaltungDetail -> VeranstaltungDetailScreen(
|
is AppScreen.VeranstaltungDetail -> VeranstaltungDetailScreen(
|
||||||
veranstaltungId = currentScreen.id,
|
veranstaltungId = currentScreen.id,
|
||||||
onBack = { onNavigate(AppScreen.Veranstaltungen) },
|
onBack = { onNavigate(AppScreen.Veranstaltungen) },
|
||||||
onTurnierNeu = { onNavigate(AppScreen.TurnierNeu(currentScreen.id)) },
|
onTurnierNeu = {
|
||||||
|
val v = at.mocode.desktop.v2.StoreV2.vereine.firstOrNull { vv ->
|
||||||
|
at.mocode.desktop.v2.StoreV2.eventsFor(vv.id).any { it.id == currentScreen.id }
|
||||||
|
}
|
||||||
|
val veranstaltung = v?.let { at.mocode.desktop.v2.StoreV2.eventsFor(it.id).firstOrNull { e -> e.id == currentScreen.id } }
|
||||||
|
val list = at.mocode.desktop.v2.TurnierStoreV2.list(currentScreen.id)
|
||||||
|
val newId = (list.maxOfOrNull { it.id } ?: 0L) + 1L
|
||||||
|
val draft = at.mocode.desktop.v2.TurnierV2(
|
||||||
|
id = newId,
|
||||||
|
veranstaltungId = currentScreen.id,
|
||||||
|
turnierNr = 0,
|
||||||
|
datumVon = veranstaltung?.datumVon ?: "",
|
||||||
|
datumBis = veranstaltung?.datumBis,
|
||||||
|
)
|
||||||
|
at.mocode.desktop.v2.TurnierStoreV2.add(currentScreen.id, draft)
|
||||||
|
onNavigate(AppScreen.TurnierDetail(currentScreen.id, newId))
|
||||||
|
},
|
||||||
onTurnierOeffnen = { tid -> onNavigate(AppScreen.TurnierDetail(currentScreen.id, tid)) },
|
onTurnierOeffnen = { tid -> onNavigate(AppScreen.TurnierDetail(currentScreen.id, tid)) },
|
||||||
)
|
)
|
||||||
is AppScreen.VeranstaltungNeu -> VeranstaltungNeuScreen(
|
is AppScreen.VeranstaltungNeu -> VeranstaltungNeuScreen(
|
||||||
|
|
@ -395,10 +449,20 @@ private fun DesktopContentArea(
|
||||||
onBack = { onNavigate(AppScreen.Veranstaltungen) }
|
onBack = { onNavigate(AppScreen.Veranstaltungen) }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
val veranstaltung = at.mocode.desktop.v2.StoreV2.eventsFor(parent.id).firstOrNull { it.id == evtId }
|
||||||
|
val blCode = parent.oepsNummer.split("-").getOrNull(1) ?: ""
|
||||||
|
val bundesland = mapOepsToBundesland(blCode)
|
||||||
TurnierDetailScreen(
|
TurnierDetailScreen(
|
||||||
veranstaltungId = evtId,
|
veranstaltungId = evtId,
|
||||||
turnierId = currentScreen.turnierId,
|
turnierId = currentScreen.turnierId,
|
||||||
onBack = { onNavigate(AppScreen.VeranstaltungUebersicht(parent.id, evtId)) },
|
onBack = { onNavigate(AppScreen.VeranstaltungUebersicht(parent.id, evtId)) },
|
||||||
|
eventVon = veranstaltung?.datumVon,
|
||||||
|
eventBis = veranstaltung?.datumBis,
|
||||||
|
eventOrt = veranstaltung?.ort,
|
||||||
|
veranstalterName = parent.name,
|
||||||
|
veranstalterOrt = parent.ort,
|
||||||
|
veranstalterBundesland = bundesland,
|
||||||
|
veranstalterLogoUrl = veranstaltung?.logoUrl,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -455,3 +519,48 @@ private fun DesktopContentArea(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DesktopFooterBar() {
|
||||||
|
// Stub-Status für MVP
|
||||||
|
val online = remember { mutableStateOf(true) }
|
||||||
|
val deviceConnected = remember { mutableStateOf(true) }
|
||||||
|
val deviceName = "Richter-Turm"
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(36.dp)
|
||||||
|
.background(Color(0xFFF3F4F6))
|
||||||
|
.padding(horizontal = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (online.value) Icons.Filled.Wifi else Icons.Filled.WifiOff,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (online.value) Color(0xFF059669) else Color(0xFFDC2626)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(6.dp))
|
||||||
|
Text(if (online.value) "Online" else "Offline", color = Color(0xFF374151), fontSize = 12.sp)
|
||||||
|
Spacer(Modifier.width(16.dp))
|
||||||
|
Icon(Icons.Filled.Devices, contentDescription = null, tint = if (deviceConnected.value) Color(0xFF2563EB) else Color(0xFF9CA3AF))
|
||||||
|
Spacer(Modifier.width(6.dp))
|
||||||
|
Text(
|
||||||
|
if (deviceConnected.value) "Verbunden: $deviceName" else "Kein Gerät verbunden",
|
||||||
|
color = Color(0xFF374151),
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
if (deviceConnected.value) {
|
||||||
|
OutlinedButton(onClick = { /* öffne Chat-Panel */ }, contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp)) {
|
||||||
|
Icon(Icons.Filled.Chat, contentDescription = null, tint = Color(0xFF2563EB))
|
||||||
|
Spacer(Modifier.width(6.dp))
|
||||||
|
Text("Chat", color = Color(0xFF2563EB), fontSize = 12.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,57 @@ fun VeranstalterDetailV2(
|
||||||
Button(onClick = onNeuVeranstaltung) { Text("+ Neue Veranstaltung") }
|
Button(onClick = onNeuVeranstaltung) { Text("+ Neue Veranstaltung") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Profil-Bereich (Logo URL, Ansprechpartner, Kontakt, Adresse)
|
||||||
|
val verein = remember(veranstalterId) { StoreV2.vereine.firstOrNull { it.id == veranstalterId } }
|
||||||
|
if (verein != null) {
|
||||||
|
Card {
|
||||||
|
Column(Modifier.fillMaxWidth().padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Text("Veranstalter‑Profil", style = MaterialTheme.typography.titleMedium)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = verein.logoUrl ?: "",
|
||||||
|
onValueChange = { verein.logoUrl = it.ifBlank { null } },
|
||||||
|
label = { Text("Logo‑URL (optional)") },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = verein.ansprechpartner ?: "",
|
||||||
|
onValueChange = { verein.ansprechpartner = it.ifBlank { null } },
|
||||||
|
label = { Text("Ansprechpartner (optional)") },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = verein.telefon ?: "",
|
||||||
|
onValueChange = { verein.telefon = it.ifBlank { null } },
|
||||||
|
label = { Text("Telefon (optional)") },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = verein.email ?: "",
|
||||||
|
onValueChange = { verein.email = it.ifBlank { null } },
|
||||||
|
label = { Text("E‑Mail (optional)") },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = verein.oepsNummer,
|
||||||
|
onValueChange = { verein.oepsNummer = it },
|
||||||
|
label = { Text("OEPS‑Nummer") },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
OutlinedTextField(
|
||||||
|
value = verein.adresse ?: "",
|
||||||
|
onValueChange = { verein.adresse = it.ifBlank { null } },
|
||||||
|
label = { Text("Adresse (optional)") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
minLines = 2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val events = StoreV2.eventsFor(veranstalterId)
|
val events = StoreV2.eventsFor(veranstalterId)
|
||||||
if (events.isEmpty()) Text("Noch keine Veranstaltungen angelegt.", color = Color(0xFF6B7280))
|
if (events.isEmpty()) Text("Noch keine Veranstaltungen angelegt.", color = Color(0xFF6B7280))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,15 @@ import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
|
|
||||||
data class Verein(
|
data class Verein(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val name: String,
|
var name: String,
|
||||||
val oepsNummer: String,
|
var oepsNummer: String,
|
||||||
val ort: String,
|
var ort: String,
|
||||||
|
// Profil-Felder (minimal laut Abstimmung)
|
||||||
|
var logoUrl: String? = null,
|
||||||
|
var ansprechpartner: String? = null,
|
||||||
|
var email: String? = null,
|
||||||
|
var telefon: String? = null,
|
||||||
|
var adresse: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class VeranstaltungV2(
|
data class VeranstaltungV2(
|
||||||
|
|
|
||||||
|
|
@ -297,6 +297,7 @@ fun VeranstaltungKonfigV2(
|
||||||
veranstalterId: Long = 0,
|
veranstalterId: Long = 0,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onSaved: (Long, Long) -> Unit, // eventId, veranstalterId
|
onSaved: (Long, Long) -> Unit, // eventId, veranstalterId
|
||||||
|
onVeranstalterCreated: (Long) -> Unit = {}, // Neuer Flow: nach Vereinsanlage ins Profil
|
||||||
) {
|
) {
|
||||||
DesktopThemeV2 {
|
DesktopThemeV2 {
|
||||||
var currentStep by remember { mutableStateOf(if (veranstalterId == 0L) 1 else 2) }
|
var currentStep by remember { mutableStateOf(if (veranstalterId == 0L) 1 else 2) }
|
||||||
|
|
@ -466,9 +467,9 @@ fun VeranstaltungKonfigV2(
|
||||||
VeranstalterAnlegenWizard(
|
VeranstalterAnlegenWizard(
|
||||||
onCancel = { showVereinNeu = false },
|
onCancel = { showVereinNeu = false },
|
||||||
onVereinCreated = { newId ->
|
onVereinCreated = { newId ->
|
||||||
selectedVereinId = newId
|
// Neuer gewünschter Flow: nach Schritt 2 ins Veranstalter‑Profil wechseln
|
||||||
showVereinNeu = false
|
showVereinNeu = false
|
||||||
currentStep = 2
|
onVeranstalterCreated(newId)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user