Feature: Zuchtkartei Profilfotos-Button — Upload, Logo setzen, Sichtbarkeit (SW by-v902)
This commit is contained in:
parent
c417891546
commit
f7a2a3861e
5 changed files with 151 additions and 8 deletions
|
|
@ -11,6 +11,7 @@ window.Page_zuchthunde = (() => {
|
|||
let _hunde = []; // geladene Hunde
|
||||
let _query = ''; // Suchtext
|
||||
let _openSections = {}; // { <hundId>: 'health'|'genetic'|'titles'|null }
|
||||
let _breederId = null; // ID des Züchter-Profils
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Hilfsfunktionen
|
||||
|
|
@ -110,6 +111,10 @@ window.Page_zuchthunde = (() => {
|
|||
<button class="btn btn-secondary btn-sm" id="zh-trial-btn" style="flex-shrink:0;white-space:nowrap">
|
||||
${UI.icon('heart-fill')} Probeverpaarung
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm" id="zh-photos-btn" style="flex-shrink:0;white-space:nowrap"
|
||||
title="Fotos & Logo für das Züchter-Profil verwalten">
|
||||
${UI.icon('images')} Profilfotos
|
||||
</button>
|
||||
<a href="/api/breeder/export" download class="btn btn-ghost btn-sm" id="zh-export-btn"
|
||||
style="flex-shrink:0" title="Alle Daten herunterladen (HTML + ODS)">
|
||||
${UI.icon('download-simple')} Export
|
||||
|
|
@ -136,6 +141,10 @@ window.Page_zuchthunde = (() => {
|
|||
document.getElementById('zh-trial-btn')?.addEventListener('click', () => _showTrialMatingModal());
|
||||
document.getElementById('zh-jahresbericht-btn')?.addEventListener('click', () => _showJahresbericht());
|
||||
document.getElementById('zh-jahresbericht-archiv-btn')?.addEventListener('click', () => _showJahresberichtArchiv());
|
||||
document.getElementById('zh-photos-btn')?.addEventListener('click', () => {
|
||||
if (!_breederId) { UI.toast.warning('Züchter-Profil noch nicht geladen.'); return; }
|
||||
_showBreederPhotosModal(_breederId);
|
||||
});
|
||||
|
||||
document.getElementById('zh-search')?.addEventListener('input', e => {
|
||||
_query = e.target.value.toLowerCase().trim();
|
||||
|
|
@ -148,7 +157,10 @@ window.Page_zuchthunde = (() => {
|
|||
// ----------------------------------------------------------
|
||||
async function _load() {
|
||||
try {
|
||||
_hunde = await API.zuchthunde.list();
|
||||
[_hunde] = await Promise.all([
|
||||
API.zuchthunde.list(),
|
||||
API.breeder.status().then(s => { _breederId = s?.id || null; }).catch(() => {}),
|
||||
]);
|
||||
_renderList();
|
||||
} catch (err) {
|
||||
const el = document.getElementById('zh-list');
|
||||
|
|
@ -1649,6 +1661,137 @@ window.Page_zuchthunde = (() => {
|
|||
document.head.appendChild(s);
|
||||
})();
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Profilfotos & Logo verwalten
|
||||
// ----------------------------------------------------------
|
||||
async function _showBreederPhotosModal(breederId) {
|
||||
const galleryId = 'bp-gallery';
|
||||
const visLabels = {
|
||||
public: { text: 'Öffentlich', color: '#16a34a' },
|
||||
inquiry: { text: 'Anfrage', color: '#f59e0b' },
|
||||
private: { text: 'Privat', color: '#6b7280' },
|
||||
};
|
||||
const visOrder = ['public', 'inquiry', 'private'];
|
||||
|
||||
UI.modal.open({
|
||||
title: `${UI.icon('images')} Züchter-Profilfotos & Logo`,
|
||||
body: `
|
||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0 0 var(--space-3)">
|
||||
Diese Fotos erscheinen im öffentlichen Züchterprofil. Das primäre Foto wird als <strong>Logo</strong> im Hero angezeigt.
|
||||
</p>
|
||||
<div id="${galleryId}" style="margin-bottom:var(--space-4)">
|
||||
<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt…</p>
|
||||
</div>
|
||||
<hr style="margin:var(--space-3) 0;border:none;border-top:1px solid var(--c-border)">
|
||||
<form id="bp-upload-form" style="display:flex;flex-direction:column;gap:var(--space-2)">
|
||||
<label style="font-size:var(--text-sm);font-weight:600">${UI.icon('upload-simple')} Foto hochladen</label>
|
||||
<input class="form-control" type="file" name="file" accept="image/*" required>
|
||||
<input class="form-control" type="text" name="caption" placeholder="Bildunterschrift (optional)">
|
||||
</form>`,
|
||||
footer: `<button type="submit" form="bp-upload-form" class="btn btn-primary" id="bp-upload-btn">
|
||||
${UI.icon('upload-simple')} Hochladen
|
||||
</button>`,
|
||||
});
|
||||
|
||||
async function _loadGallery() {
|
||||
const el = document.getElementById(galleryId);
|
||||
if (!el) return;
|
||||
try {
|
||||
const photos = await API.breederPhotos.list('breeder', breederId);
|
||||
if (!photos.length) {
|
||||
el.innerHTML = `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Fotos — lade das erste hoch.</p>`;
|
||||
return;
|
||||
}
|
||||
el.innerHTML = `
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:var(--space-2)">
|
||||
${photos.map(ph => {
|
||||
const thumb = ph.thumbnail_url || ph.url || '';
|
||||
const vis = visLabels[ph.visibility] || visLabels.private;
|
||||
const isPrimary = ph.is_primary;
|
||||
return `
|
||||
<div style="position:relative;border-radius:var(--radius-md);overflow:hidden;
|
||||
border:${isPrimary ? '2px solid var(--c-primary)' : '1px solid var(--c-border)'};aspect-ratio:1"
|
||||
data-photo-id="${ph.id}">
|
||||
<a href="${_esc(ph.url || '')}" target="_blank" rel="noopener noreferrer">
|
||||
<img src="${_esc(thumb)}" alt="${_esc(ph.caption || '')}"
|
||||
loading="lazy" style="width:100%;height:100%;object-fit:cover;display:block"
|
||||
onerror="this.parentElement.parentElement.style.opacity='.4'">
|
||||
</a>
|
||||
${isPrimary ? `<span style="position:absolute;top:3px;left:3px;background:var(--c-primary);color:white;
|
||||
font-size:9px;font-weight:700;border-radius:999px;padding:1px 5px">Logo</span>` : ''}
|
||||
<!-- Sichtbarkeit -->
|
||||
<button class="bp-vis-btn" data-photo-id="${ph.id}" data-vis="${_esc(ph.visibility)}"
|
||||
style="position:absolute;bottom:0;left:0;right:0;background:${vis.color};color:#fff;
|
||||
border:none;cursor:pointer;font-size:9px;padding:2px 4px;font-weight:700">
|
||||
${vis.text}
|
||||
</button>
|
||||
<!-- Als Logo setzen -->
|
||||
${!isPrimary ? `<button class="bp-primary-btn" data-photo-id="${ph.id}" title="Als Logo setzen"
|
||||
style="position:absolute;top:2px;right:24px;background:rgba(0,0,0,.5);color:#fff;
|
||||
border:none;border-radius:50%;cursor:pointer;width:20px;height:20px;
|
||||
display:flex;align-items:center;justify-content:center;font-size:10px">
|
||||
${UI.icon('star')}
|
||||
</button>` : ''}
|
||||
<!-- Löschen -->
|
||||
<button class="bp-del-btn" data-photo-id="${ph.id}" title="Löschen"
|
||||
style="position:absolute;top:2px;right:2px;background:rgba(0,0,0,.5);color:#fff;
|
||||
border:none;border-radius:50%;cursor:pointer;width:20px;height:20px;
|
||||
display:flex;align-items:center;justify-content:center;font-size:10px">
|
||||
${UI.icon('x')}
|
||||
</button>
|
||||
</div>`;
|
||||
}).join('')}
|
||||
</div>`;
|
||||
|
||||
el.querySelectorAll('.bp-vis-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const next = visOrder[(visOrder.indexOf(btn.dataset.vis) + 1) % visOrder.length];
|
||||
try { await API.breederPhotos.updateVisibility(parseInt(btn.dataset.photoId), next); _loadGallery(); }
|
||||
catch (err) { UI.toast.error(err.message); }
|
||||
});
|
||||
});
|
||||
el.querySelectorAll('.bp-primary-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
try { await API.breederPhotos.setPrimary(parseInt(btn.dataset.photoId)); _loadGallery(); UI.toast.success('Als Logo gesetzt.'); }
|
||||
catch (err) { UI.toast.error(err.message); }
|
||||
});
|
||||
});
|
||||
el.querySelectorAll('.bp-del-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!window.confirm('Foto löschen?')) return;
|
||||
try { await API.breederPhotos.remove(parseInt(btn.dataset.photoId)); _loadGallery(); }
|
||||
catch (err) { UI.toast.error(err.message); }
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
const el = document.getElementById(galleryId);
|
||||
if (el) el.innerHTML = `<p style="color:var(--c-danger)">${_esc(err.message || 'Fehler')}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
_loadGallery();
|
||||
|
||||
document.getElementById('bp-upload-form')?.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('bp-upload-btn');
|
||||
const fileInput = e.target.querySelector('[name="file"]');
|
||||
const caption = e.target.querySelector('[name="caption"]')?.value?.trim() || '';
|
||||
if (!fileInput?.files?.length) { UI.toast.error('Bitte Datei auswählen.'); return; }
|
||||
const fd = new FormData();
|
||||
fd.append('entity_type', 'breeder');
|
||||
fd.append('entity_id', String(breederId));
|
||||
fd.append('visibility', 'public');
|
||||
fd.append('caption', caption);
|
||||
fd.append('file', fileInput.files[0]);
|
||||
await UI.asyncButton(btn, async () => {
|
||||
await API.breederPhotos.upload(fd);
|
||||
UI.toast.success('Foto hochgeladen.');
|
||||
e.target.reset();
|
||||
await _loadGallery();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return { init, refresh, onDogChange };
|
||||
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue