fix: Beschreibung generieren — AbortController, Readonly, Gold-Styling

- Textarea readonly + visuell gedimmt während Generierung läuft
- AbortController bricht Request ab wenn Modal geschlossen wird
- Stern-Icon entfernt, Button-Text in Gold (Akzentfarbe)
- api.js _request() unterstützt externen AbortSignal
Dieser Commit ist enthalten in:
Claude Dev
2026-03-27 23:46:44 +01:00
Ursprung a84e2c108e
Commit 11d0aadc57
4 geänderte Dateien mit 48 neuen und 7 gelöschten Zeilen

Datei anzeigen

@@ -2082,6 +2082,22 @@ a:hover {
align-items: center; align-items: center;
} }
#btn-enhance-description {
color: var(--accent-primary);
border-color: var(--accent-primary);
font-weight: 600;
}
#btn-enhance-description:hover:not(:disabled) {
background: var(--accent-primary);
color: #fff;
}
.textarea--loading {
opacity: 0.5;
cursor: wait;
}
.spinner-inline { .spinner-inline {
display: inline-block; display: inline-block;
width: 14px; width: 14px;

Datei anzeigen

@@ -337,7 +337,7 @@
<textarea id="inc-description" placeholder="Weitere Details zum Vorfall (optional)"></textarea> <textarea id="inc-description" placeholder="Weitere Details zum Vorfall (optional)"></textarea>
<div class="description-enhance-row"> <div class="description-enhance-row">
<button type="button" class="btn btn-secondary btn-small" id="btn-enhance-description" onclick="App.generateDescription()" disabled> <button type="button" class="btn btn-secondary btn-small" id="btn-enhance-description" onclick="App.generateDescription()" disabled>
<span id="enhance-btn-text">&#10022; Beschreibung generieren</span> <span id="enhance-btn-text">Beschreibung generieren</span>
<span id="enhance-spinner" class="spinner-inline" style="display:none;"></span> <span id="enhance-spinner" class="spinner-inline" style="display:none;"></span>
</button> </button>
</div> </div>

Datei anzeigen

@@ -12,10 +12,15 @@ const API = {
}; };
}, },
async _request(method, path, body = null) { async _request(method, path, body = null, externalSignal = null) {
const controller = new AbortController(); const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000); const timeout = setTimeout(() => controller.abort(), 30000);
// Externen Abort weiterleiten an internen Controller
if (externalSignal) {
externalSignal.addEventListener('abort', () => controller.abort(), { once: true });
}
const options = { const options = {
method, method,
headers: this._getHeaders(), headers: this._getHeaders(),
@@ -70,8 +75,8 @@ const API = {
return this._request('GET', `/incidents${query}`); return this._request('GET', `/incidents${query}`);
}, },
enhanceDescription(title, description, type) { enhanceDescription(title, description, type, signal = null) {
return this._request('POST', '/incidents/enhance-description', { title, description, type }); return this._request('POST', '/incidents/enhance-description', { title, description, type }, signal);
}, },
createIncident(data) { createIncident(data) {

Datei anzeigen

@@ -1617,21 +1617,34 @@ async generateDescription() {
const btn = document.getElementById('btn-enhance-description'); const btn = document.getElementById('btn-enhance-description');
const btnText = document.getElementById('enhance-btn-text'); const btnText = document.getElementById('enhance-btn-text');
const spinner = document.getElementById('enhance-spinner'); const spinner = document.getElementById('enhance-spinner');
const textarea = document.getElementById('inc-description');
if (title.length < 3 || !btn) return; if (title.length < 3 || !btn) return;
// Vorherigen Request abbrechen falls noch aktiv
if (this._enhanceController) this._enhanceController.abort();
this._enhanceController = new AbortController();
btn.disabled = true; btn.disabled = true;
btnText.style.display = 'none'; btnText.style.display = 'none';
spinner.style.display = ''; spinner.style.display = '';
textarea.readOnly = true;
textarea.classList.add('textarea--loading');
try { try {
const result = await API.enhanceDescription(title, description || null, type); const result = await API.enhanceDescription(title, description || null, type, this._enhanceController.signal);
document.getElementById('inc-description').value = result.description; textarea.value = result.description;
} catch (err) { } catch (err) {
UI.showToast('Beschreibung konnte nicht generiert werden', 'error'); if (err.name !== 'AbortError') {
UI.showToast('Beschreibung konnte nicht generiert werden', 'error');
}
} finally { } finally {
btnText.style.display = ''; btnText.style.display = '';
spinner.style.display = 'none'; spinner.style.display = 'none';
btn.disabled = title.length < 3; btn.disabled = title.length < 3;
textarea.readOnly = false;
textarea.classList.remove('textarea--loading');
this._enhanceController = null;
} }
}, },
@@ -3029,6 +3042,13 @@ function openModal(id) {
} }
function closeModal(id) { function closeModal(id) {
// Laufenden Beschreibung-generieren-Request abbrechen
if (id === 'modal-new' && App._enhanceController) {
App._enhanceController.abort();
App._enhanceController = null;
const ta = document.getElementById('inc-description');
if (ta) { ta.readOnly = false; ta.classList.remove('textarea--loading'); }
}
const modal = document.getElementById(id); const modal = document.getElementById(id);
releaseFocus(modal); releaseFocus(modal);
modal.classList.remove('active'); modal.classList.remove('active');