Jobangebote: Favoriten (Stern) und Startseiten-Sektion ganz oben
Stellen lassen sich per Stern merken — unabhängig vom Status (offen / uebernommen / abgelehnt). Neue Spalte jobangebote.favorit (PRAGMA-Migration), JSON-Toggle-Endpunkt /jobangebote/:id/favorit, Stern-Button auf den Karten sowie eine "Gemerkte Stellen"-Sektion ganz oben auf der Startseite. Toggle laeuft per fetch mit optimistischem UI; beim Entfernen auf der Startseite verschwindet die Zeile sofort. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
<style>
|
||||
/* Stern-Button: neutral grau, gemerkt amber. Klasse .is-fav steuert den
|
||||
Zustand; das Skript unten schaltet sie per Klick um (optimistisch). */
|
||||
.fav-star { color: rgb(107 114 128); } /* gray-500 */
|
||||
.fav-star:hover { background-color: rgb(243 244 246); } /* gray-100 */
|
||||
.dark .fav-star { color: rgb(209 213 219); }
|
||||
.dark .fav-star:hover { background-color: rgb(55 65 81); } /* gray-700 */
|
||||
.fav-star.is-fav {
|
||||
color: rgb(217 119 6); /* amber-600 */
|
||||
background-color: rgb(255 251 235); /* amber-50 */
|
||||
border-color: rgb(252 211 77); /* amber-300 */
|
||||
}
|
||||
.dark .fav-star.is-fav {
|
||||
color: rgb(252 211 77); /* amber-300 */
|
||||
background-color: rgba(245, 158, 11, 0.10);
|
||||
border-color: rgba(251, 191, 36, 0.40);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
(function () {
|
||||
if (window.__favStarBound) return;
|
||||
window.__favStarBound = true;
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('[data-favorit]');
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
var id = btn.getAttribute('data-favorit');
|
||||
var aktiv = btn.getAttribute('data-aktiv') === '1';
|
||||
|
||||
// Optimistisch umschalten, sofort sichtbar.
|
||||
setzeBtn(btn, !aktiv);
|
||||
|
||||
fetch('/jobangebote/' + encodeURIComponent(id) + '/favorit', {
|
||||
method: 'POST',
|
||||
headers: { 'X-Requested-With': 'fetch' }
|
||||
})
|
||||
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
|
||||
.then(function (r) {
|
||||
if (r.ok && r.json && r.json.success) {
|
||||
setzeBtn(btn, !!r.json.favorit);
|
||||
// Auf der Startseite: beim Entfernen die Zeile ausblenden
|
||||
// und die ganze Sektion einräumen, wenn sie leer wird.
|
||||
if (!r.json.favorit) entferneFavoritZeile(btn);
|
||||
} else {
|
||||
setzeBtn(btn, aktiv); // Server hat abgelehnt -> zurückrollen
|
||||
if (r.json && r.json.error) console.warn('Favorit:', r.json.error);
|
||||
}
|
||||
})
|
||||
.catch(function () { setzeBtn(btn, aktiv); });
|
||||
});
|
||||
|
||||
function setzeBtn(btn, aktiv) {
|
||||
btn.setAttribute('data-aktiv', aktiv ? '1' : '0');
|
||||
btn.setAttribute('aria-pressed', aktiv ? 'true' : 'false');
|
||||
btn.setAttribute('title', aktiv ? 'Favorit entfernen' : 'Als Favorit merken');
|
||||
btn.classList.toggle('is-fav', !!aktiv);
|
||||
var ico = btn.querySelector('.fav-ico');
|
||||
if (ico) ico.setAttribute('fill', aktiv ? 'currentColor' : 'none');
|
||||
var lbl = btn.querySelector('.fav-label');
|
||||
if (lbl) lbl.textContent = aktiv ? 'Gemerkt' : 'Merken';
|
||||
}
|
||||
|
||||
function entferneFavoritZeile(btn) {
|
||||
var item = btn.closest('[data-fav-item]');
|
||||
if (!item) return; // nicht auf der Startseite -> nur optisch umschalten
|
||||
var card = document.getElementById('favoritenCard');
|
||||
item.remove();
|
||||
if (card && !card.querySelectorAll('[data-fav-item]').length) {
|
||||
card.style.transition = 'opacity 0.2s ease';
|
||||
card.style.opacity = '0';
|
||||
setTimeout(function () { card.remove(); }, 180);
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user