Web-GUI: fixierte Gruppen anzeigen, Strafprofil waehlen, Farben nutzen
CSVConfig.HandleGaesteliste liefert zusaetzlich FixierteGruppen (Name -> Tisch/Anzahl), damit fest platzierte Gaeste in der Gaesteliste sichtbar (grau hinterlegt) erscheinen. Durchgaengig "VIP" -> "fixiert" umbenannt. Backend: Strafprofile werden gecacht geladen, neuer Endpoint /strafprofile und Query-Parameter strafen an /berechnen, damit sich Profile (default vs. locker) in der GUI vergleichen lassen. SVG-Ausgabe nutzt das Farbschema aus farben.cfg (Rueckfall auf Palette). Frontend: tische.ini und CSV werden bei Auswahl sofort geladen, Auswahl der fixierten Gruppen und des Optimierungsprofils in der Oberflaeche. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+46
-17
@@ -61,6 +61,7 @@
|
||||
padding: .3rem .7rem; margin: .15rem .3rem .15rem 0; font-size: .9rem; }
|
||||
.kennzahl b { color: var(--akzent); }
|
||||
.hinweis { font-size: .8rem; color: #6b6557; }
|
||||
#gruppenUebersicht tr.fixiert td { background: #e6e3dc; color: #555; }
|
||||
.varianten { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .6rem; }
|
||||
.variante {
|
||||
border: 1px solid var(--rand); border-radius: 6px; background: white;
|
||||
@@ -97,10 +98,9 @@
|
||||
<button type="button" class="sekundaer" onclick="presetReihe()">Preset: 4er-Reihe</button>
|
||||
<button type="button" class="sekundaer" onclick="presetRaster()">Preset: 3×3-Raster</button>
|
||||
<br>
|
||||
<label class="hinweis">Oder eine bestehende <code>tische.ini</code> laden
|
||||
(z. B. aus <code>work/hochzeit</code>):</label>
|
||||
<input type="file" id="tischDatei" accept=".ini,text/plain">
|
||||
<button type="button" class="sekundaer" onclick="tischDateiLaden()">tische.ini laden</button>
|
||||
<label class="hinweis">Oder eine bestehende <code>tische.ini</code> auswählen
|
||||
(z. B. aus <code>work/hochzeit</code>) — wird sofort geladen:</label>
|
||||
<input type="file" id="tischDatei" accept=".ini,text/plain" onchange="tischDateiLaden()">
|
||||
<br>
|
||||
<button type="button" onclick="tischeSpeichern()">Tische speichern</button>
|
||||
<div class="status" id="tischStatus"></div>
|
||||
@@ -110,9 +110,10 @@
|
||||
<h2>2. Gästeliste (CSV)</h2>
|
||||
<p class="hinweis">Spalten: <code>Vorname;Nachname;Titel;Gruppe;Tisch;Anzahl</code> —
|
||||
<code>Gruppe</code> hält Personen zusammen (leer = Einzelgast),
|
||||
<code>Tisch</code> setzt VIPs fest (z. B. Brautpaar an Tisch 1),
|
||||
<code>Tisch</code> fixiert Gäste an einem Tisch (z. B. Brautpaar an Tisch 1),
|
||||
<code>Anzahl</code> expandiert eine Zeile in mehrere Buchungen
|
||||
(anonyme Sammelbuchung). Datei hochladen <em>oder</em> direkt hier bearbeiten:</p>
|
||||
(anonyme Sammelbuchung). Eine Datei auswählen (wird sofort übernommen)
|
||||
<em>oder</em> hier direkt bearbeiten und übernehmen:</p>
|
||||
<textarea id="csvText"></textarea>
|
||||
<br>
|
||||
<input type="file" id="csvDatei" accept=".csv,text/csv">
|
||||
@@ -128,7 +129,11 @@
|
||||
Sitzordnungen zum Vergleich — die Wertigkeit (0 = perfekt, negativer = schlechter),
|
||||
die Zahl getrennter Gruppen und allein sitzende Personen machen sie vergleichbar.
|
||||
Variante anklicken, um Tische und Raumplan umzuschalten; jeder weitere Klick auf
|
||||
„Berechnen“ startet einen frischen Lauf.</p>
|
||||
„Berechnen“ startet einen frischen Lauf. Über das <em>Optimierungsprofil</em> lassen sich
|
||||
Bewertungs-Vorgehen vergleichen (z. B. <code>default</code> vs.
|
||||
<code>locker</code>): Profil wählen und neu berechnen.</p>
|
||||
<label class="hinweis">Optimierungsprofil:
|
||||
<select id="strafenWahl"></select></label>
|
||||
<button type="button" id="berechnenKnopf" onclick="berechnen()" disabled>Sitzordnung berechnen</button>
|
||||
<div class="status" id="rechnenStatus"></div>
|
||||
<div class="varianten" id="variantenListe"></div>
|
||||
@@ -259,6 +264,7 @@ async function tischDateiLaden() {
|
||||
'„Tische speichern“ ist nur nach Änderungen nötig.', 'ok');
|
||||
berechnenFreigeben();
|
||||
} catch (e) { status('tischStatus', 'Fehler: ' + e.message, 'fehler'); }
|
||||
finally { eingabe.value = ''; } // damit dieselbe Datei erneut auswählbar ist
|
||||
}
|
||||
|
||||
/* ------------------------------------------------ 2. Gäste */
|
||||
@@ -293,11 +299,10 @@ async function csvDateiGewaehlt(ev) {
|
||||
}
|
||||
text = text.replace(/^/, '');
|
||||
document.getElementById('csvText').value = text;
|
||||
// stale Ausgabe eines frueheren Uploads entfernen
|
||||
document.getElementById('gruppenUebersicht').innerHTML = '';
|
||||
status('gaesteStatus', 'Datei „' + datei.name + '“ geladen — zum Übernehmen ' +
|
||||
'„Gästeliste übernehmen“ klicken.', 'ok');
|
||||
ev.target.value = ''; // Textarea ist ab jetzt die alleinige Quelle
|
||||
ev.target.value = ''; // damit dieselbe Datei erneut auswählbar ist
|
||||
// direkt uebernehmen: die ausgewaehlte Datei wird sofort geladen (die
|
||||
// Textarea ist bereits befuellt und dient gaesteHochladen als Quelle)
|
||||
await gaesteHochladen();
|
||||
}
|
||||
|
||||
async function gaesteHochladen() {
|
||||
@@ -308,16 +313,35 @@ async function gaesteHochladen() {
|
||||
try {
|
||||
const r = await api('/sitzung/' + sitzung + '/personen', { method: 'POST', body: form });
|
||||
status('gaesteStatus', '✓ ' + r.personen + ' Personen in ' + r.gruppen.length +
|
||||
' Gruppen, dazu ' + r.vips + ' VIPs mit festem Tisch.', 'ok');
|
||||
' Gruppen' + (r.fixiert ? ', dazu ' + r.fixiert +
|
||||
' fixierte Personen an festen Tischen' : '') + '.', 'ok');
|
||||
// regulaere Gruppen, danach die fest platzierten ("fixierten") Gruppen
|
||||
// grau hinterlegt
|
||||
const regulaer = r.gruppen.map(g =>
|
||||
'<tr><td>' + g.name + '</td><td>' + g.anzahl + '</td></tr>').join('');
|
||||
const fixiert = (r.fixierte || []).map(f =>
|
||||
'<tr class="fixiert"><td>' + f.name +
|
||||
' <span class="hinweis">(fixiert an Tisch ' + f.tisch + ')</span></td><td>' +
|
||||
f.anzahl + '</td></tr>').join('');
|
||||
const html = '<table><thead><tr><th>Gruppe</th><th>Personen</th></tr></thead><tbody>' +
|
||||
r.gruppen.map(g => '<tr><td>' + g.name + '</td><td>' + g.anzahl + '</td></tr>').join('') +
|
||||
'</tbody></table>';
|
||||
regulaer + fixiert + '</tbody></table>';
|
||||
document.getElementById('gruppenUebersicht').innerHTML = html;
|
||||
berechnenFreigeben();
|
||||
} catch (e) { status('gaesteStatus', 'Fehler: ' + e.message, 'fehler'); }
|
||||
}
|
||||
|
||||
/* ------------------------------------------------ 3. Berechnen */
|
||||
async function strafprofileLaden() {
|
||||
// Auswahl der Strafprofile aus strafen.cfg fuellen (z.B. default, locker)
|
||||
try {
|
||||
const r = await api('/strafprofile');
|
||||
const sel = document.getElementById('strafenWahl');
|
||||
sel.innerHTML = r.profile.map(p =>
|
||||
'<option value="' + p + '"' + (p === r.vorgabe ? ' selected' : '') +
|
||||
'>' + p + '</option>').join('');
|
||||
} catch (e) { /* Auswahl bleibt leer -> Backend nutzt sein Default */ }
|
||||
}
|
||||
|
||||
async function berechnenFreigeben() {
|
||||
try {
|
||||
const s = await api('/sitzung/' + sitzung);
|
||||
@@ -332,9 +356,12 @@ async function berechnen() {
|
||||
knopf.disabled = true;
|
||||
status('rechnenStatus', 'Der genetische Algorithmus läuft…');
|
||||
try {
|
||||
const r = await api('/sitzung/' + sitzung + '/berechnen?varianten=3', { method: 'POST' });
|
||||
const profil = document.getElementById('strafenWahl').value || 'default';
|
||||
const r = await api('/sitzung/' + sitzung + '/berechnen?varianten=3&strafen=' +
|
||||
encodeURIComponent(profil), { method: 'POST' });
|
||||
letzterLauf = r;
|
||||
status('rechnenStatus', '✓ Lauf ' + r.lauf + ': ' + r.varianten.length +
|
||||
status('rechnenStatus', '✓ Lauf ' + r.lauf + ' (Optimierungsprofil „' + r.strafen + '“): ' +
|
||||
r.varianten.length +
|
||||
(r.varianten.length === 1
|
||||
? ' Variante gefunden (der Lauf konvergierte auf eine Sitzordnung).'
|
||||
: ' unterschiedliche Varianten zum Vergleich.'), 'ok');
|
||||
@@ -367,6 +394,7 @@ async function zeigeVariante(i) {
|
||||
|
||||
document.getElementById('kennzahlen').innerHTML =
|
||||
'<span class="kennzahl">Variante: <b>' + v.nr + '</b> (Lauf ' + letzterLauf.lauf + ')</span>' +
|
||||
'<span class="kennzahl">Optimierungsprofil: <b>' + letzterLauf.strafen + '</b></span>' +
|
||||
'<span class="kennzahl">Wertigkeit: <b>' + v.kennzahlen.wertigkeit + '</b></span>' +
|
||||
'<span class="kennzahl">getrennte Gruppen: <b>' + v.kennzahlen.getrennteGruppen + '</b></span>' +
|
||||
'<span class="kennzahl">allein sitzend: <b>' + v.kennzahlen.alleinePersonen + '</b></span>';
|
||||
@@ -439,6 +467,7 @@ function gruppeInteraktion(svg) {
|
||||
window.addEventListener('DOMContentLoaded', async () => {
|
||||
document.getElementById('csvText').value = beispielCSV;
|
||||
document.getElementById('csvDatei').addEventListener('change', csvDateiGewaehlt);
|
||||
strafprofileLaden();
|
||||
try {
|
||||
sitzung = await sitzungHolen();
|
||||
document.getElementById('sitzungsId').textContent = sitzung;
|
||||
|
||||
Reference in New Issue
Block a user