Feature: Warteliste pro Wurf — CRUD, Status-Flow, Formular (SW by-v891)
This commit is contained in:
parent
e8c2d5b940
commit
67e68bbe2d
8 changed files with 324 additions and 7 deletions
|
|
@ -206,6 +206,14 @@ window.Page_litters = (() => {
|
|||
});
|
||||
});
|
||||
|
||||
el.querySelectorAll('.litters-waitlist-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => _toggleWaitlist(parseInt(btn.dataset.id)));
|
||||
});
|
||||
|
||||
el.querySelectorAll('.litters-add-waitlist-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => _showWaitlistForm(parseInt(btn.dataset.id), null));
|
||||
});
|
||||
|
||||
// Aufgeklappten Wurf wiederherstellen
|
||||
if (_openId) _togglePuppies(_openId, true);
|
||||
}
|
||||
|
|
@ -248,6 +256,10 @@ window.Page_litters = (() => {
|
|||
title="Welpen anzeigen">
|
||||
${UI.icon('caret-down')} Welpen
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm litters-waitlist-btn" data-id="${l.id}"
|
||||
title="Warteliste">
|
||||
${UI.icon('list-bullets')} Warteliste
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm litters-photos-btn" data-id="${l.id}"
|
||||
title="Wurf-Fotos verwalten">
|
||||
${UI.icon('images')} Fotos
|
||||
|
|
@ -281,6 +293,15 @@ window.Page_litters = (() => {
|
|||
${UI.icon('plus')} Welpen hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
<div class="litters-waitlist-wrap" id="waitlist-wrap-${l.id}" style="display:none">
|
||||
<div class="litters-waitlist-inner" id="waitlist-inner-${l.id}">
|
||||
<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt…</p>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-sm litters-add-waitlist-btn" data-id="${l.id}"
|
||||
style="margin-top:var(--space-3)">
|
||||
${UI.icon('plus')} Interessent eintragen
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
@ -561,6 +582,180 @@ window.Page_litters = (() => {
|
|||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Warteliste
|
||||
// ----------------------------------------------------------
|
||||
const _WL_STATUS = {
|
||||
anfrage: { label: 'Anfrage', color: '#6b7280' },
|
||||
vorgemerkt: { label: 'Vorgemerkt', color: '#f59e0b' },
|
||||
bestaetigt: { label: 'Bestätigt', color: '#3b82f6' },
|
||||
abgegeben: { label: 'Abgegeben', color: '#16a34a' },
|
||||
abgesagt: { label: 'Abgesagt', color: '#dc2626' },
|
||||
};
|
||||
|
||||
function _wlStatusBadge(status) {
|
||||
const s = _WL_STATUS[status] || _WL_STATUS.anfrage;
|
||||
return `<span style="background:${s.color}1a;color:${s.color};border:1px solid ${s.color}40;border-radius:999px;padding:1px 8px;font-size:var(--text-xs);font-weight:600">${s.label}</span>`;
|
||||
}
|
||||
|
||||
async function _toggleWaitlist(litterId) {
|
||||
const wrap = document.getElementById(`waitlist-wrap-${litterId}`);
|
||||
if (!wrap) return;
|
||||
const isOpen = wrap.style.display !== 'none';
|
||||
if (isOpen) { wrap.style.display = 'none'; return; }
|
||||
wrap.style.display = '';
|
||||
await _loadWaitlist(litterId);
|
||||
}
|
||||
|
||||
async function _loadWaitlist(litterId) {
|
||||
const inner = document.getElementById(`waitlist-inner-${litterId}`);
|
||||
if (!inner) return;
|
||||
try {
|
||||
const entries = await API.litters.waitlist(litterId);
|
||||
_renderWaitlist(inner, litterId, entries);
|
||||
} catch (err) {
|
||||
inner.innerHTML = `<p style="color:var(--c-danger);font-size:var(--text-sm)">${_esc(err.message || 'Fehler.')}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
function _renderWaitlist(container, litterId, entries) {
|
||||
if (!entries.length) {
|
||||
container.innerHTML = `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Interessenten eingetragen.</p>`;
|
||||
return;
|
||||
}
|
||||
container.innerHTML = `
|
||||
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
|
||||
${entries.map((e, i) => `
|
||||
<div style="background:var(--c-bg-secondary);border-radius:var(--radius-md);padding:var(--space-3) var(--space-3);display:flex;gap:var(--space-3);align-items:flex-start" data-entry-id="${e.id}">
|
||||
<div style="background:var(--c-primary);color:white;border-radius:50%;width:1.6rem;height:1.6rem;display:flex;align-items:center;justify-content:center;font-size:var(--text-xs);font-weight:700;flex-shrink:0;margin-top:2px">${i + 1}</div>
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-1)">
|
||||
<span style="font-weight:600;font-size:var(--text-sm)">${_esc(e.name)}</span>
|
||||
${_wlStatusBadge(e.status)}
|
||||
${e.wunsch_geschlecht && e.wunsch_geschlecht !== 'egal' ? `<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">${e.wunsch_geschlecht === 'maennlich' ? '♂ Rüde' : '♀ Hündin'}</span>` : ''}
|
||||
${e.wunsch_farbe ? `<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(e.wunsch_farbe)}</span>` : ''}
|
||||
</div>
|
||||
<div style="display:flex;gap:var(--space-4);flex-wrap:wrap;font-size:var(--text-xs);color:var(--c-text-secondary)">
|
||||
${e.email ? `<span>${UI.icon('envelope')} ${_esc(e.email)}</span>` : ''}
|
||||
${e.telefon ? `<span>${UI.icon('phone')} ${_esc(e.telefon)}</span>` : ''}
|
||||
<span>${UI.icon('calendar-dots')} ${e.created_at ? e.created_at.slice(0, 10) : '—'}</span>
|
||||
</div>
|
||||
${e.nachricht ? `<div style="margin-top:var(--space-1);font-size:var(--text-xs);color:var(--c-text-secondary);font-style:italic">"${_esc(e.nachricht)}"</div>` : ''}
|
||||
${e.notiz ? `<div style="margin-top:var(--space-1);font-size:var(--text-xs);background:var(--c-warning-bg,#fffbeb);color:#92400e;border-radius:4px;padding:2px 6px">${UI.icon('note-pencil')} ${_esc(e.notiz)}</div>` : ''}
|
||||
</div>
|
||||
<div style="display:flex;gap:var(--space-1);flex-shrink:0">
|
||||
<button class="btn btn-ghost btn-xs wl-edit-btn" data-entry-id="${e.id}" title="Bearbeiten">${UI.icon('pencil-simple')}</button>
|
||||
<button class="btn btn-ghost btn-xs wl-delete-btn" data-entry-id="${e.id}" title="Entfernen" style="color:var(--c-danger)">${UI.icon('trash')}</button>
|
||||
</div>
|
||||
</div>`).join('')}
|
||||
</div>`;
|
||||
|
||||
container.querySelectorAll('.wl-edit-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const entry = entries.find(e => e.id === parseInt(btn.dataset.entryId));
|
||||
if (entry) _showWaitlistForm(litterId, entry);
|
||||
});
|
||||
});
|
||||
|
||||
container.querySelectorAll('.wl-delete-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!window.confirm('Interessenten aus der Warteliste entfernen?')) return;
|
||||
try {
|
||||
await API.litters.removeWaitlist(parseInt(btn.dataset.entryId));
|
||||
await _loadWaitlist(litterId);
|
||||
} catch (err) { UI.toast.error(err.message || 'Fehler.'); }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _showWaitlistForm(litterId, entry) {
|
||||
const isEdit = !!entry;
|
||||
const v = entry || {};
|
||||
UI.modal.open({
|
||||
title: isEdit ? 'Interessent bearbeiten' : 'Interessent eintragen',
|
||||
body: `
|
||||
<form id="wl-form" style="display:flex;flex-direction:column;gap:var(--space-3)">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Name *</label>
|
||||
<input class="form-control" name="name" required value="${_esc(v.name || '')}">
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
|
||||
<div class="form-group">
|
||||
<label class="form-label">E-Mail</label>
|
||||
<input class="form-control" type="email" name="email" value="${_esc(v.email || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Telefon</label>
|
||||
<input class="form-control" name="telefon" value="${_esc(v.telefon || '')}">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Wunsch Geschlecht</label>
|
||||
<select class="form-control" name="wunsch_geschlecht">
|
||||
<option value="egal" ${(!v.wunsch_geschlecht || v.wunsch_geschlecht === 'egal') ? 'selected' : ''}>Egal</option>
|
||||
<option value="maennlich" ${v.wunsch_geschlecht === 'maennlich' ? 'selected' : ''}>Rüde ♂</option>
|
||||
<option value="weiblich" ${v.wunsch_geschlecht === 'weiblich' ? 'selected' : ''}>Hündin ♀</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Wunsch Farbe</label>
|
||||
<input class="form-control" name="wunsch_farbe" placeholder="z.B. schwarz-weiß" value="${_esc(v.wunsch_farbe || '')}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Nachricht des Interessenten</label>
|
||||
<textarea class="form-control" name="nachricht" rows="2" placeholder="Was hat der Interessent geschrieben?">${_esc(v.nachricht || '')}</textarea>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Status</label>
|
||||
<select class="form-control" name="status">
|
||||
${Object.entries(_WL_STATUS).map(([k, s]) => `<option value="${k}" ${(v.status || 'anfrage') === k ? 'selected' : ''}>${s.label}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Position</label>
|
||||
<input class="form-control" type="number" name="prioritaet" min="0" value="${v.prioritaet ?? 0}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Interne Notiz</label>
|
||||
<input class="form-control" name="notiz" placeholder="Nur für dich sichtbar" value="${_esc(v.notiz || '')}">
|
||||
</div>
|
||||
</form>`,
|
||||
footer: `
|
||||
<button class="btn btn-secondary" onclick="UI.modal.close()">Abbrechen</button>
|
||||
<button class="btn btn-primary" form="wl-form" type="submit">${isEdit ? 'Speichern' : 'Eintragen'}</button>`,
|
||||
});
|
||||
|
||||
document.getElementById('wl-form').addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const fd = new FormData(e.target);
|
||||
const data = {
|
||||
name: fd.get('name')?.trim(),
|
||||
email: fd.get('email')?.trim() || null,
|
||||
telefon: fd.get('telefon')?.trim() || null,
|
||||
nachricht: fd.get('nachricht')?.trim() || null,
|
||||
wunsch_geschlecht: fd.get('wunsch_geschlecht'),
|
||||
wunsch_farbe: fd.get('wunsch_farbe')?.trim() || null,
|
||||
prioritaet: parseInt(fd.get('prioritaet')) || 0,
|
||||
status: fd.get('status'),
|
||||
notiz: fd.get('notiz')?.trim() || null,
|
||||
};
|
||||
try {
|
||||
if (isEdit) {
|
||||
await API.litters.updateWaitlist(entry.id, data);
|
||||
} else {
|
||||
await API.litters.addWaitlist(litterId, data);
|
||||
}
|
||||
UI.modal.close();
|
||||
await _loadWaitlist(litterId);
|
||||
UI.toast.success(isEdit ? 'Gespeichert.' : 'Interessent eingetragen.');
|
||||
} catch (err) { UI.toast.error(err.message || 'Fehler.'); }
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Wurf-Formular (neu / bearbeiten)
|
||||
// ----------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue